From 48df35723bf74ae389ccb69606670b7eead89b26 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 22 Feb 2026 11:51:10 +0100 Subject: [PATCH 1/3] Assert ExprCacheHelper->import() will not mix up same expr-string based attributes --- tests/PHPStan/Parser/CachedParserTest.php | 56 +++++++++++++++++++ .../PHPStan/Parser/data/parser-cache-bug.php | 23 ++++++++ 2 files changed, 79 insertions(+) create mode 100644 tests/PHPStan/Parser/data/parser-cache-bug.php diff --git a/tests/PHPStan/Parser/CachedParserTest.php b/tests/PHPStan/Parser/CachedParserTest.php index 29ca21ee3a..44c3b81b32 100644 --- a/tests/PHPStan/Parser/CachedParserTest.php +++ b/tests/PHPStan/Parser/CachedParserTest.php @@ -5,8 +5,13 @@ use Generator; use PhpParser\Node; use PhpParser\Node\Stmt\Namespace_; +use PHPStan\BetterReflection\BetterReflection; +use PHPStan\BetterReflection\Reflection\ExprCacheHelper; +use PHPStan\BetterReflection\SourceLocator\SourceStubber\PhpStormStubsSourceStubber; use PHPStan\File\FileHelper; use PHPStan\File\FileReader; +use PHPStan\Node\Printer\Printer; +use PHPStan\Php\PhpVersion; use PHPStan\Testing\PHPStanTestCase; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\MockObject\Stub; @@ -123,4 +128,55 @@ public function testParseTheSameFileWithDifferentMethod(): void $this->assertSame(2, $stmts[0]->stmts[1]->expr->expr->class->getAttribute(AnonymousClassVisitor::ATTRIBUTE_LINE_INDEX)); } + public function testWithExprCacheHelper(): void + { + $fileHelper = self::getContainer()->getByType(FileHelper::class); + $pathRoutingParser = new PathRoutingParser( + $fileHelper, + self::getContainer()->getService('currentPhpVersionRichParser'), + self::getContainer()->getService('currentPhpVersionSimpleDirectParser'), + self::getContainer()->getService('php8Parser'), + null, + ); + $parser = new CachedParser($pathRoutingParser, 500); + $path = $fileHelper->normalizePath(__DIR__ . '/data/parser-cache-bug.php'); + $pathRoutingParser->setAnalysedFiles([$path]); + $contents = FileReader::read($path); + $stmts = $parser->parseString($contents); + + $this->assertInstanceOf(Namespace_::class, $stmts[0]); + $ns = $stmts[0]; + + $this->assertInstanceOf(Node\Stmt\Class_::class, $ns->stmts[1]); + $class = $ns->stmts[1]; + + $this->assertInstanceOf(Node\Stmt\Property::class, $class->stmts[0]); + $property = $class->stmts[0]; + $this->assertInstanceOf(Node\AttributeGroup::class, $property->attrGroups[0]); + $group = $property->attrGroups[0]; + $this->assertInstanceOf(Node\Attribute::class, $group->attrs[0]); + $attribute = $group->attrs[0]; + + $expr = $attribute->args[0]->value; + $this->assertSame(['startLine' => 8, 'startTokenPos' => 21, 'startFilePos' => 88, 'endLine' => 8, 'endTokenPos' => 21, 'endFilePos' => 94, 'kind' => 1, 'rawValue' => "'hello'"], $expr->getAttributes()); + $exported = ExprCacheHelper::export($expr); + $reImported = ExprCacheHelper::import($exported); + $this->assertSame(['startLine' => 8, 'startTokenPos' => 21, 'startFilePos' => 88, 'endLine' => 8, 'endTokenPos' => 21, 'endFilePos' => 94, 'kind' => 1, 'rawValue' => "'hello'"], $reImported->getAttributes()); + + $this->assertInstanceOf(Node\Stmt\Property::class, $class->stmts[1]); + $property = $class->stmts[1]; + $this->assertInstanceOf(Node\AttributeGroup::class, $property->attrGroups[0]); + $group = $property->attrGroups[0]; + $this->assertInstanceOf(Node\Attribute::class, $group->attrs[0]); + $attribute = $group->attrs[0]; + + $expr = $attribute->args[0]->value; + $this->assertSame(['startLine' => 10, 'startTokenPos' => 35, 'startFilePos' => 137, 'endLine' => 10, 'endTokenPos' => 35, 'endFilePos' => 143, 'kind' => 1, 'rawValue' => "'hello'"], $expr->getAttributes()); + $exported = ExprCacheHelper::export($expr); + unset($exported['attributes']['startLine']); // modify attributes + $reImported = ExprCacheHelper::import($exported); + // assert that we get back the default start-line instead of a stale cached startLine of previous same value expression + $this->assertSame(['startLine' => 1, 'startTokenPos' => 35, 'startFilePos' => 137, 'endLine' => 10, 'endTokenPos' => 35, 'endFilePos' => 143, 'kind' => 1, 'rawValue' => "'hello'"], $reImported->getAttributes()); + } + } diff --git a/tests/PHPStan/Parser/data/parser-cache-bug.php b/tests/PHPStan/Parser/data/parser-cache-bug.php new file mode 100644 index 0000000000..02957046da --- /dev/null +++ b/tests/PHPStan/Parser/data/parser-cache-bug.php @@ -0,0 +1,23 @@ +arg = $event; + } +} From 6ac44e44326f4f39a6e49296a83bbcf26a8a9747 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 22 Feb 2026 11:53:54 +0100 Subject: [PATCH 2/3] cs --- tests/PHPStan/Parser/CachedParserTest.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/PHPStan/Parser/CachedParserTest.php b/tests/PHPStan/Parser/CachedParserTest.php index 44c3b81b32..9a6bfa2331 100644 --- a/tests/PHPStan/Parser/CachedParserTest.php +++ b/tests/PHPStan/Parser/CachedParserTest.php @@ -5,13 +5,9 @@ use Generator; use PhpParser\Node; use PhpParser\Node\Stmt\Namespace_; -use PHPStan\BetterReflection\BetterReflection; use PHPStan\BetterReflection\Reflection\ExprCacheHelper; -use PHPStan\BetterReflection\SourceLocator\SourceStubber\PhpStormStubsSourceStubber; use PHPStan\File\FileHelper; use PHPStan\File\FileReader; -use PHPStan\Node\Printer\Printer; -use PHPStan\Php\PhpVersion; use PHPStan\Testing\PHPStanTestCase; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\MockObject\Stub; From 94b7929b6100c6e5802e14cf1a45a207127885a0 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 22 Feb 2026 11:57:31 +0100 Subject: [PATCH 3/3] simplify --- tests/PHPStan/Parser/CachedParserTest.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/PHPStan/Parser/CachedParserTest.php b/tests/PHPStan/Parser/CachedParserTest.php index 9a6bfa2331..df3097d3cb 100644 --- a/tests/PHPStan/Parser/CachedParserTest.php +++ b/tests/PHPStan/Parser/CachedParserTest.php @@ -148,9 +148,7 @@ public function testWithExprCacheHelper(): void $this->assertInstanceOf(Node\Stmt\Property::class, $class->stmts[0]); $property = $class->stmts[0]; - $this->assertInstanceOf(Node\AttributeGroup::class, $property->attrGroups[0]); $group = $property->attrGroups[0]; - $this->assertInstanceOf(Node\Attribute::class, $group->attrs[0]); $attribute = $group->attrs[0]; $expr = $attribute->args[0]->value; @@ -161,9 +159,7 @@ public function testWithExprCacheHelper(): void $this->assertInstanceOf(Node\Stmt\Property::class, $class->stmts[1]); $property = $class->stmts[1]; - $this->assertInstanceOf(Node\AttributeGroup::class, $property->attrGroups[0]); $group = $property->attrGroups[0]; - $this->assertInstanceOf(Node\Attribute::class, $group->attrs[0]); $attribute = $group->attrs[0]; $expr = $attribute->args[0]->value;