Skip to content

Commit

Permalink
Fix anonymous class with PHPDoc
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Jul 29, 2020
1 parent 153c874 commit 643646a
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 30 deletions.
2 changes: 1 addition & 1 deletion src/PhpDoc/StubValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
2 changes: 1 addition & 1 deletion src/Reflection/ClassReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -888,7 +888,7 @@ private function getImplementsTags(): array
}

/** @return array<string,TemplateTag> */
private function getTemplateTags(): array
public function getTemplateTags(): array
{
$resolvedPhpDoc = $this->getResolvedPhpDoc();
if ($resolvedPhpDoc === null) {
Expand Down
41 changes: 15 additions & 26 deletions src/Rules/Generics/ClassTemplateTypeRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -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<InClassNode>
*/
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)
);
}

Expand Down
22 changes: 20 additions & 2 deletions tests/PHPStan/Rules/Generics/ClassTemplateTypeRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
use PHPStan\Rules\ClassCaseSensitivityCheck;
use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;
use PHPStan\Type\FileTypeMapper;

/**
* @extends \PHPStan\Testing\RuleTestCase<ClassTemplateTypeRule>
Expand All @@ -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)
);
}
Expand Down Expand Up @@ -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,
],
]);
}

Expand Down
25 changes: 25 additions & 0 deletions tests/PHPStan/Rules/Generics/data/class-template.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{

};

0 comments on commit 643646a

Please sign in to comment.