From b8a841b9ff1ca9b478e6df8eecb80395458a43d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tinjo=20Sch=C3=B6ni?= <32767367+tscni@users.noreply.github.com> Date: Fri, 23 Aug 2024 00:10:56 +0200 Subject: [PATCH 1/3] Repair PhpParser\Node\Stmt\Class_::isAnonymous() --- src/Analyser/NodeScopeResolver.php | 3 +-- src/Node/AnonymousClassNode.php | 18 ++++++++++++++++++ src/Parser/AnonymousClassVisitor.php | 18 ++++++++++++++---- src/Type/FileTypeMapper.php | 5 ++--- .../AnonymousClassReflectionTest.php | 3 +-- 5 files changed, 36 insertions(+), 11 deletions(-) create mode 100644 src/Node/AnonymousClassNode.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 140dc7e02b..64d0dd9ec1 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -117,7 +117,6 @@ use PHPStan\Node\UnreachableStatementNode; use PHPStan\Node\VariableAssignNode; use PHPStan\Node\VarTagChangedExpressionTypeNode; -use PHPStan\Parser\AnonymousClassVisitor; use PHPStan\Parser\ArrowFunctionArgVisitor; use PHPStan\Parser\ClosureArgVisitor; use PHPStan\Parser\Parser; @@ -856,7 +855,7 @@ private function processStmtNode( if ($stmt->name === null) { throw new ShouldNotHappenException(); } - if ($stmt->getAttribute(AnonymousClassVisitor::ATTRIBUTE_ANONYMOUS_CLASS, false) === false) { + if (!$stmt->isAnonymous()) { $classReflection = $this->reflectionProvider->getClass($stmt->name->toString()); } else { $classReflection = $this->reflectionProvider->getAnonymousClassReflection($stmt, $scope); diff --git a/src/Node/AnonymousClassNode.php b/src/Node/AnonymousClassNode.php new file mode 100644 index 0000000000..b67da335b4 --- /dev/null +++ b/src/Node/AnonymousClassNode.php @@ -0,0 +1,18 @@ +> */ + /** @var array> */ private array $nodesPerLine = []; public function beforeTraverse(array $nodes): ?array @@ -27,10 +27,20 @@ public function enterNode(Node $node): ?Node return null; } - $node->setAttribute(self::ATTRIBUTE_ANONYMOUS_CLASS, true); + $node = new AnonymousClassNode( + $node->name, + [ + 'flags' => $node->flags, + 'extends' => $node->extends, + 'implements' => $node->implements, + 'stmts' => $node->stmts, + 'attrGroups' => $node->attrGroups, + ], + $node->getAttributes(), + ); $this->nodesPerLine[$node->getStartLine()][] = $node; - return null; + return $node; } public function afterTraverse(array $nodes): ?array diff --git a/src/Type/FileTypeMapper.php b/src/Type/FileTypeMapper.php index 07df40e471..3e0c73a93e 100644 --- a/src/Type/FileTypeMapper.php +++ b/src/Type/FileTypeMapper.php @@ -8,7 +8,6 @@ use PHPStan\BetterReflection\Util\GetLastDocComment; use PHPStan\Broker\AnonymousClassNameHelper; use PHPStan\File\FileHelper; -use PHPStan\Parser\AnonymousClassVisitor; use PHPStan\Parser\Parser; use PHPStan\PhpDoc\PhpDocNodeResolver; use PHPStan\PhpDoc\PhpDocStringResolver; @@ -261,7 +260,7 @@ function (Node $node) use ($fileName, $lookForTrait, &$traitFound, $traitMethodA } $className = $this->anonymousClassNameHelper->getAnonymousClassName($node, $fileName); - } elseif ((bool) $node->getAttribute(AnonymousClassVisitor::ATTRIBUTE_ANONYMOUS_CLASS, false)) { + } elseif ($node instanceof Node\Stmt\Class_ && $node->isAnonymous()) { $className = $node->name->name; } else { if ($traitFound) { @@ -452,7 +451,7 @@ function (Node $node) use ($fileName, $lookForTrait, $phpDocNodeMap, &$traitFoun } $className = $this->anonymousClassNameHelper->getAnonymousClassName($node, $fileName); - } elseif ((bool) $node->getAttribute(AnonymousClassVisitor::ATTRIBUTE_ANONYMOUS_CLASS, false)) { + } elseif ($node instanceof Node\Stmt\Class_ && $node->isAnonymous()) { $className = $node->name->name; } else { if ($traitFound) { diff --git a/tests/PHPStan/Reflection/AnonymousClassReflectionTest.php b/tests/PHPStan/Reflection/AnonymousClassReflectionTest.php index 6a07961d4e..25c3a48afe 100644 --- a/tests/PHPStan/Reflection/AnonymousClassReflectionTest.php +++ b/tests/PHPStan/Reflection/AnonymousClassReflectionTest.php @@ -5,7 +5,6 @@ use PhpParser\Node; use PhpParser\Node\Stmt\Class_; use PHPStan\Analyser\Scope; -use PHPStan\Parser\AnonymousClassVisitor; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Testing\RuleTestCase; @@ -36,7 +35,7 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { - if (!(bool) $node->getAttribute(AnonymousClassVisitor::ATTRIBUTE_ANONYMOUS_CLASS)) { + if (!$node->isAnonymous()) { return []; } From 60afe71284e067697a8df0c499d30d6eeaa8a4a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tinjo=20Sch=C3=B6ni?= <32767367+tscni@users.noreply.github.com> Date: Fri, 23 Aug 2024 01:04:53 +0200 Subject: [PATCH 2/3] Resolve backward compatibility issues with sub nodes --- src/Node/AnonymousClassNode.php | 14 ++++++++++++++ src/Parser/AnonymousClassVisitor.php | 12 +----------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/Node/AnonymousClassNode.php b/src/Node/AnonymousClassNode.php index b67da335b4..b5d8333496 100644 --- a/src/Node/AnonymousClassNode.php +++ b/src/Node/AnonymousClassNode.php @@ -10,6 +10,20 @@ final class AnonymousClassNode extends Class_ { + public static function createFromClassNode(Class_ $node): self + { + $subNodes = []; + foreach ($node->getSubNodeNames() as $subNodeName) { + $subNodes[$subNodeName] = $node->$subNodeName; + } + + return new AnonymousClassNode( + $node->name, + $subNodes, + $node->getAttributes(), + ); + } + public function isAnonymous(): bool { return true; diff --git a/src/Parser/AnonymousClassVisitor.php b/src/Parser/AnonymousClassVisitor.php index e2108322ae..055bad405b 100644 --- a/src/Parser/AnonymousClassVisitor.php +++ b/src/Parser/AnonymousClassVisitor.php @@ -27,17 +27,7 @@ public function enterNode(Node $node): ?Node return null; } - $node = new AnonymousClassNode( - $node->name, - [ - 'flags' => $node->flags, - 'extends' => $node->extends, - 'implements' => $node->implements, - 'stmts' => $node->stmts, - 'attrGroups' => $node->attrGroups, - ], - $node->getAttributes(), - ); + $node = AnonymousClassNode::createFromClassNode($node); $this->nodesPerLine[$node->getStartLine()][] = $node; return $node; From c8c5359ea8c94b17d167df4b600590a73a76f4fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tinjo=20Sch=C3=B6ni?= <32767367+tscni@users.noreply.github.com> Date: Fri, 23 Aug 2024 13:24:47 +0200 Subject: [PATCH 3/3] Keep anonymousClass attribute for backward compatibility --- src/Parser/AnonymousClassVisitor.php | 1 + tests/PHPStan/Reflection/AnonymousClassReflectionTest.php | 3 +++ 2 files changed, 4 insertions(+) diff --git a/src/Parser/AnonymousClassVisitor.php b/src/Parser/AnonymousClassVisitor.php index 055bad405b..33ca16c567 100644 --- a/src/Parser/AnonymousClassVisitor.php +++ b/src/Parser/AnonymousClassVisitor.php @@ -28,6 +28,7 @@ public function enterNode(Node $node): ?Node } $node = AnonymousClassNode::createFromClassNode($node); + $node->setAttribute('anonymousClass', true); // We keep this for backward compatibility $this->nodesPerLine[$node->getStartLine()][] = $node; return $node; diff --git a/tests/PHPStan/Reflection/AnonymousClassReflectionTest.php b/tests/PHPStan/Reflection/AnonymousClassReflectionTest.php index 25c3a48afe..9310f617f1 100644 --- a/tests/PHPStan/Reflection/AnonymousClassReflectionTest.php +++ b/tests/PHPStan/Reflection/AnonymousClassReflectionTest.php @@ -8,6 +8,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Testing\RuleTestCase; +use PHPUnit\Framework\Assert; use function implode; use function sprintf; @@ -39,6 +40,8 @@ public function processNode(Node $node, Scope $scope): array return []; } + Assert::assertTrue($node->getAttribute('anonymousClass')); + $classReflection = $this->reflectionProvider->getAnonymousClassReflection($node, $scope); return [