From 643646a6c8f5bf38af6627b03108526d441875e9 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 29 Jul 2020 22:39:12 +0200 Subject: [PATCH] Fix anonymous class with PHPDoc --- src/PhpDoc/StubValidator.php | 2 +- src/Reflection/ClassReflection.php | 2 +- src/Rules/Generics/ClassTemplateTypeRule.php | 41 +++++++------------ .../Generics/ClassTemplateTypeRuleTest.php | 22 +++++++++- .../Rules/Generics/data/class-template.php | 25 +++++++++++ 5 files changed, 62 insertions(+), 30 deletions(-) diff --git a/src/PhpDoc/StubValidator.php b/src/PhpDoc/StubValidator.php index aac6ec7558..db39713d4d 100644 --- a/src/PhpDoc/StubValidator.php +++ b/src/PhpDoc/StubValidator.php @@ -128,7 +128,7 @@ private function getRuleRegistry(Container $container): Registry // level 2 new ClassAncestorsRule($fileTypeMapper, $genericAncestorsCheck), - new ClassTemplateTypeRule($fileTypeMapper, $templateTypeCheck), + new ClassTemplateTypeRule($templateTypeCheck), new FunctionTemplateTypeRule($fileTypeMapper, $templateTypeCheck), new FunctionSignatureVarianceRule($varianceCheck), new InterfaceAncestorsRule($fileTypeMapper, $genericAncestorsCheck), diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index dffb6d312d..2e9d39c4c6 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -888,7 +888,7 @@ private function getImplementsTags(): array } /** @return array */ - private function getTemplateTags(): array + public function getTemplateTags(): array { $resolvedPhpDoc = $this->getResolvedPhpDoc(); if ($resolvedPhpDoc === null) { diff --git a/src/Rules/Generics/ClassTemplateTypeRule.php b/src/Rules/Generics/ClassTemplateTypeRule.php index 1b4e1ea618..ec726ce894 100644 --- a/src/Rules/Generics/ClassTemplateTypeRule.php +++ b/src/Rules/Generics/ClassTemplateTypeRule.php @@ -4,62 +4,51 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\Node\InClassNode; use PHPStan\Rules\Rule; -use PHPStan\Type\FileTypeMapper; use PHPStan\Type\Generic\TemplateTypeScope; /** - * @implements \PHPStan\Rules\Rule<\PhpParser\Node\Stmt\Class_> + * @implements \PHPStan\Rules\Rule */ class ClassTemplateTypeRule implements Rule { - private \PHPStan\Type\FileTypeMapper $fileTypeMapper; - private \PHPStan\Rules\Generics\TemplateTypeCheck $templateTypeCheck; public function __construct( - FileTypeMapper $fileTypeMapper, TemplateTypeCheck $templateTypeCheck ) { - $this->fileTypeMapper = $fileTypeMapper; $this->templateTypeCheck = $templateTypeCheck; } public function getNodeType(): string { - return Node\Stmt\Class_::class; + return InClassNode::class; } public function processNode(Node $node, Scope $scope): array { - $docComment = $node->getDocComment(); - if ($docComment === null) { + if (!$scope->isInClass()) { return []; } - - if (!isset($node->namespacedName)) { - throw new \PHPStan\ShouldNotHappenException(); + $classReflection = $scope->getClassReflection(); + $className = $classReflection->getName(); + if ($classReflection->isAnonymous()) { + $displayName = 'anonymous class'; + } else { + $displayName = 'class ' . $classReflection->getDisplayName(); } - $className = (string) $node->namespacedName; - $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( - $scope->getFile(), - $className, - null, - null, - $docComment->getText() - ); - return $this->templateTypeCheck->check( $node, TemplateTypeScope::createWithClass($className), - $resolvedPhpDoc->getTemplateTags(), - sprintf('PHPDoc tag @template for class %s cannot have existing class %%s as its name.', $className), - sprintf('PHPDoc tag @template for class %s cannot have existing type alias %%s as its name.', $className), - sprintf('PHPDoc tag @template %%s for class %s has invalid bound type %%s.', $className), - sprintf('PHPDoc tag @template %%s for class %s with bound type %%s is not supported.', $className) + $classReflection->getTemplateTags(), + sprintf('PHPDoc tag @template for %s cannot have existing class %%s as its name.', $displayName), + sprintf('PHPDoc tag @template for %s cannot have existing type alias %%s as its name.', $displayName), + sprintf('PHPDoc tag @template %%s for %s has invalid bound type %%s.', $displayName), + sprintf('PHPDoc tag @template %%s for %s with bound type %%s is not supported.', $displayName) ); } diff --git a/tests/PHPStan/Rules/Generics/ClassTemplateTypeRuleTest.php b/tests/PHPStan/Rules/Generics/ClassTemplateTypeRuleTest.php index b17ce01b1d..a256539b41 100644 --- a/tests/PHPStan/Rules/Generics/ClassTemplateTypeRuleTest.php +++ b/tests/PHPStan/Rules/Generics/ClassTemplateTypeRuleTest.php @@ -5,7 +5,6 @@ use PHPStan\Rules\ClassCaseSensitivityCheck; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; -use PHPStan\Type\FileTypeMapper; /** * @extends \PHPStan\Testing\RuleTestCase @@ -18,7 +17,6 @@ protected function getRule(): Rule $broker = $this->createReflectionProvider(); return new ClassTemplateTypeRule( - self::getContainer()->getByType(FileTypeMapper::class), new TemplateTypeCheck($broker, new ClassCaseSensitivityCheck($broker), ['TypeAlias' => 'int'], true) ); } @@ -46,6 +44,26 @@ public function testRule(): void 'PHPDoc tag @template for class ClassTemplateType\Ipsum cannot have existing type alias TypeAlias as its name.', 40, ], + [ + 'PHPDoc tag @template for anonymous class cannot have existing class stdClass as its name.', + 45, + ], + [ + 'PHPDoc tag @template T for anonymous class has invalid bound type ClassTemplateType\Zazzzu.', + 50, + ], + [ + 'PHPDoc tag @template T for anonymous class with bound type int is not supported.', + 55, + ], + [ + 'Class ClassTemplateType\Baz referenced with incorrect case: ClassTemplateType\baz.', + 60, + ], + [ + 'PHPDoc tag @template for anonymous class cannot have existing type alias TypeAlias as its name.', + 65, + ], ]); } diff --git a/tests/PHPStan/Rules/Generics/data/class-template.php b/tests/PHPStan/Rules/Generics/data/class-template.php index 10adfa2f0f..b1ac19ec8b 100644 --- a/tests/PHPStan/Rules/Generics/data/class-template.php +++ b/tests/PHPStan/Rules/Generics/data/class-template.php @@ -41,3 +41,28 @@ class Ipsum { } + +new /** @template stdClass */ class +{ + +}; + +new /** @template T of Zazzzu */ class +{ + +}; + +new /** @template T of int */ class +{ + +}; + +new /** @template T of baz */ class +{ + +}; + +new /** @template TypeAlias */ class +{ + +};