diff --git a/src/PhpDocParser/PhpDoc.php b/src/PhpDocParser/PhpDoc.php index 83f22d4..721c93e 100644 --- a/src/PhpDocParser/PhpDoc.php +++ b/src/PhpDocParser/PhpDoc.php @@ -4,36 +4,284 @@ namespace Typhoon\Reflection\PhpDocParser; +use PHPStan\PhpDocParser\Ast\PhpDoc\ExtendsTagValueNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\ImplementsTagValueNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode; use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; +use Typhoon\Reflection\TagPrioritizer; +use Typhoon\Reflection\Variance; /** * @internal * @psalm-internal Typhoon\Reflection - * @psalm-immutable */ final class PhpDoc { private static ?self $empty = null; + private TypeNode|null|false $varType = false; + + /** + * @var ?array + */ + private ?array $paramTypes = null; + + private TypeNode|null|false $returnType = false; + + /** + * @var ?list + */ + private ?array $templates = null; + + /** + * @var ?list + */ + private ?array $extendedTypes = null; + /** - * @param array $paramTypes - * @param array $templates - * @param list $extendedTypes - * @param list $implementedTypes + * @var ?list + */ + private ?array $implementedTypes = null; + + /** + * @param array $tags */ public function __construct( - public readonly ?TypeNode $varType = null, - public readonly array $paramTypes = [], - public readonly ?TypeNode $returnType = null, - public readonly array $templates = [], - public readonly array $extendedTypes = [], - public readonly array $implementedTypes = [], + private readonly TagPrioritizer $tagPrioritizer, + private array $tags, ) {} public static function empty(): self { - return self::$empty ??= new self(); + return self::$empty ??= new self( + tagPrioritizer: new TagPrioritizer\PHPStanOverPsalmOverOthersTagPrioritizer(), + tags: [], + ); + } + + public function varType(): ?TypeNode + { + if ($this->varType !== false) { + return $this->varType; + } + + $varTag = null; + + foreach ($this->tags as $key => $tag) { + if (!$tag->value instanceof VarTagValueNode) { + continue; + } + + /** @var PhpDocTagNode $tag */ + if ($this->shouldReplaceTag($varTag, $tag)) { + $varTag = $tag; + } + + unset($this->tags[$key]); + } + + return $this->varType = $varTag?->value->type; + } + + /** + * @return array + */ + public function paramTypes(): array + { + if ($this->paramTypes !== null) { + return $this->paramTypes; + } + + $paramTags = []; + + foreach ($this->tags as $key => $tag) { + if (!$tag->value instanceof ParamTagValueNode) { + continue; + } + + /** @var PhpDocTagNode $tag */ + $name = $tag->value->parameterName; + \assert(($name[0] ?? '') === '$'); + $name = substr($name, 1); + \assert($name !== ''); + + if ($this->shouldReplaceTag($paramTags[$name] ?? null, $tag)) { + $paramTags[$name] = $tag; + } + + unset($this->tags[$key]); + } + + return $this->paramTypes = array_map( + static fn (PhpDocTagNode $tag): TypeNode => $tag->value->type, + $paramTags, + ); + } + + public function returnType(): ?TypeNode + { + if ($this->returnType !== false) { + return $this->returnType; + } + + $returnTag = null; + + foreach ($this->tags as $key => $tag) { + if (!$tag->value instanceof ReturnTagValueNode) { + continue; + } + + /** @var PhpDocTagNode $tag */ + if ($this->shouldReplaceTag($returnTag, $tag)) { + $returnTag = $tag; + } + + unset($this->tags[$key]); + } + + return $this->returnType = $returnTag?->value->type; + } + + /** + * @return list + */ + public function templates(): array + { + if ($this->templates !== null) { + return $this->templates; + } + + $templateTags = []; + + foreach ($this->tags as $key => $tag) { + if (!$tag->value instanceof TemplateTagValueNode) { + continue; + } + + /** @var PhpDocTagNode $tag */ + if ($this->shouldReplaceTag($templateTags[$tag->value->name] ?? null, $tag)) { + $templateTags[$tag->value->name] = $tag; + } + + unset($this->tags[$key]); + } + + return $this->templates = array_map( + static function (PhpDocTagNode $tag): TemplateTagValueNode { + $tag->value->setAttribute('variance', match (true) { + str_ends_with($tag->name, 'covariant') => Variance::COVARIANT, + str_ends_with($tag->name, 'contravariant') => Variance::CONTRAVARIANT, + default => Variance::INVARIANT, + }); + + return $tag->value; + }, + array_values($templateTags), + ); + } + + /** + * @return list + */ + public function templateNames(): array + { + return array_column($this->templates(), 'name'); + } + + /** + * @return list + */ + public function extendedTypes(): array + { + if ($this->extendedTypes !== null) { + return $this->extendedTypes; + } + + $extendsTags = []; + + foreach ($this->tags as $key => $tag) { + if (!$tag->value instanceof ExtendsTagValueNode) { + continue; + } + + /** @var PhpDocTagNode $tag */ + $name = $tag->value->type->type->name; + + if ($this->shouldReplaceTag($extendsTags[$name] ?? null, $tag)) { + $extendsTags[$name] = $tag; + } + + unset($this->tags[$key]); + } + + return $this->extendedTypes = array_map( + static fn (PhpDocTagNode $tag): GenericTypeNode => $tag->value->type, + array_values($extendsTags), + ); + } + + /** + * @return list + */ + public function implementedTypes(): array + { + if ($this->implementedTypes !== null) { + return $this->implementedTypes; + } + + $implementsTags = []; + + foreach ($this->tags as $key => $tag) { + if (!$tag->value instanceof ImplementsTagValueNode) { + continue; + } + + /** @var PhpDocTagNode $tag */ + $name = $tag->value->type->type->name; + + if ($this->shouldReplaceTag($implementsTags[$name] ?? null, $tag)) { + $implementsTags[$name] = $tag; + } + + unset($this->tags[$key]); + } + + return $this->implementedTypes = array_map( + static fn (PhpDocTagNode $tag): GenericTypeNode => $tag->value->type, + array_values($implementsTags), + ); + } + + /** + * @template TCurrentValueNode of PhpDocTagValueNode + * @template TNewValueNode of PhpDocTagValueNode + * @param PhpDocTagNode $currentTag + * @param PhpDocTagNode $newTag + */ + private function shouldReplaceTag(?PhpDocTagNode $currentTag, PhpDocTagNode $newTag): bool + { + return $currentTag === null || $this->priorityOf($newTag) >= $this->priorityOf($currentTag); + } + + /** + * @template TValueNode of PhpDocTagValueNode + * @param PhpDocTagNode $tag + */ + private function priorityOf(PhpDocTagNode $tag): int + { + $priority = $tag->getAttribute('priority'); + + if (!\is_int($priority)) { + $priority = $this->tagPrioritizer->priorityFor($tag->name); + $tag->setAttribute('priority', $priority); + } + + return $priority; } } diff --git a/src/PhpDocParser/PhpDocBuilder.php b/src/PhpDocParser/PhpDocBuilder.php deleted file mode 100644 index 58a7071..0000000 --- a/src/PhpDocParser/PhpDocBuilder.php +++ /dev/null @@ -1,244 +0,0 @@ - - */ - private ?PhpDocTagNode $varTag = null; - - /** - * @var array> - */ - private array $paramTags = []; - - /** - * @var ?PhpDocTagNode - */ - private ?PhpDocTagNode $returnTag = null; - - /** - * @var array> - */ - private array $templateTags = []; - - /** - * @var array> - */ - private array $extendsTags = []; - - /** - * @var array> - */ - private array $implementsTags = []; - - public function __construct( - private readonly TagPrioritizer $tagPrioritizer, - ) {} - - /** - * @param array $tags - */ - public function addTags(array $tags): self - { - foreach ($tags as $tag) { - $this->addTag($tag); - } - - return $this; - } - - public function build(): PhpDoc - { - return new PhpDoc( - varType: $this->varTag?->value->type, - paramTypes: array_map( - static fn (PhpDocTagNode $tag): TypeNode => $tag->value->type, - $this->paramTags, - ), - returnType: $this->returnTag?->value->type, - templates: array_map( - static function (PhpDocTagNode $tag): TemplateTagValueNode { - $tag->value->setAttribute('variance', match (true) { - str_ends_with($tag->name, 'covariant') => Variance::COVARIANT, - str_ends_with($tag->name, 'contravariant') => Variance::CONTRAVARIANT, - default => Variance::INVARIANT, - }); - - return $tag->value; - }, - $this->templateTags, - ), - extendedTypes: array_map( - static fn (PhpDocTagNode $tag): GenericTypeNode => $tag->value->type, - array_values($this->extendsTags), - ), - implementedTypes: array_map( - static fn (PhpDocTagNode $tag): GenericTypeNode => $tag->value->type, - array_values($this->implementsTags), - ), - ); - } - - private function addTag(PhpDocTagNode $tag): void - { - if ($tag->value instanceof VarTagValueNode) { - /** @var PhpDocTagNode $tag */ - $this->addVarTag($tag); - - return; - } - - if ($tag->value instanceof ParamTagValueNode) { - /** @var PhpDocTagNode $tag */ - $this->addParamTag($tag); - - return; - } - - if ($tag->value instanceof ReturnTagValueNode) { - /** @var PhpDocTagNode $tag */ - $this->addReturnTag($tag); - - return; - } - - if ($tag->value instanceof TemplateTagValueNode) { - /** @var PhpDocTagNode $tag */ - $this->addTemplateTag($tag); - - return; - } - - if ($tag->value instanceof ExtendsTagValueNode) { - /** @var PhpDocTagNode $tag */ - $this->addExtendsTag($tag); - - return; - } - - if ($tag->value instanceof ImplementsTagValueNode) { - /** @var PhpDocTagNode $tag */ - $this->addImplementsTag($tag); - - return; - } - } - - /** - * @param PhpDocTagNode $tag - */ - private function addVarTag(PhpDocTagNode $tag): void - { - if ($this->shouldReplaceTag($this->varTag, $tag)) { - $this->varTag = $tag; - } - } - - /** - * @param PhpDocTagNode $tag - */ - private function addParamTag(PhpDocTagNode $tag): void - { - $name = $tag->value->parameterName; - \assert(($name[0] ?? '') === '$'); - $name = substr($name, 1); - \assert($name !== ''); - - if ($this->shouldReplaceTag($this->paramTags[$name] ?? null, $tag)) { - $this->paramTags[$name] = $tag; - } - } - - /** - * @param PhpDocTagNode $tag - */ - private function addReturnTag(PhpDocTagNode $tag): void - { - if ($this->shouldReplaceTag($this->returnTag, $tag)) { - $this->returnTag = $tag; - } - } - - /** - * @param PhpDocTagNode $tag - */ - private function addTemplateTag(PhpDocTagNode $tag): void - { - if ($this->shouldReplaceTag($this->templateTags[$tag->value->name] ?? null, $tag)) { - $this->templateTags[$tag->value->name] = $tag; - } - } - - /** - * @param PhpDocTagNode $tag - */ - private function addExtendsTag(PhpDocTagNode $tag): void - { - $name = $tag->value->type->type->name; - - if ($this->shouldReplaceTag($this->extendsTags[$name] ?? null, $tag)) { - $this->extendsTags[$name] = $tag; - } - } - - /** - * @param PhpDocTagNode $tag - */ - private function addImplementsTag(PhpDocTagNode $tag): void - { - $name = $tag->value->type->type->name; - - if ($this->shouldReplaceTag($this->implementsTags[$name] ?? null, $tag)) { - $this->implementsTags[$name] = $tag; - } - } - - /** - * @template TCurrentValueNode of PhpDocTagValueNode - * @template TNewValueNode of PhpDocTagValueNode - * @param PhpDocTagNode $currentTag - * @param PhpDocTagNode $newTag - */ - private function shouldReplaceTag(?PhpDocTagNode $currentTag, PhpDocTagNode $newTag): bool - { - return $currentTag === null || $this->priorityOf($newTag) >= $this->priorityOf($currentTag); - } - - /** - * @template TValueNode of PhpDocTagValueNode - * @param PhpDocTagNode $tag - */ - private function priorityOf(PhpDocTagNode $tag): int - { - $priority = $tag->getAttribute('priority'); - - if (!\is_int($priority)) { - $priority = $this->tagPrioritizer->priorityFor($tag->name); - $tag->setAttribute('priority', $priority); - } - - return $priority; - } -} diff --git a/src/PhpDocParser/PhpDocParser.php b/src/PhpDocParser/PhpDocParser.php index 0a0e707..b38ce40 100644 --- a/src/PhpDocParser/PhpDocParser.php +++ b/src/PhpDocParser/PhpDocParser.php @@ -31,8 +31,8 @@ public function __construct( public function parsePhpDoc(string $phpDoc): PhpDoc { $tokens = $this->lexer->tokenize($phpDoc); - $tags = $this->parser->parse(new TokenIterator($tokens))->getTags(); + $phpDoc = $this->parser->parse(new TokenIterator($tokens)); - return (new PhpDocBuilder($this->tagPrioritizer))->addTags($tags)->build(); + return new PhpDoc($this->tagPrioritizer, $phpDoc->getTags()); } } diff --git a/src/Reflector/NameContextVisitor.php b/src/Reflector/NameContextVisitor.php index 42cd5b6..02b9389 100644 --- a/src/Reflector/NameContextVisitor.php +++ b/src/Reflector/NameContextVisitor.php @@ -60,7 +60,7 @@ public function enterNode(Node $node): ?int $this->nameContext->enterClass( name: $node->name->name, parent: $node instanceof Stmt\Class_ ? $node->extends?->toCodeString() : null, - templateNames: array_keys(PhpDocParsingVisitor::fromNode($node)->templates), + templateNames: PhpDocParsingVisitor::fromNode($node)->templateNames(), ); return null; @@ -69,7 +69,7 @@ public function enterNode(Node $node): ?int if ($node instanceof Stmt\ClassMethod) { $this->nameContext->enterMethod( name: $node->name->name, - templateNames: array_keys(PhpDocParsingVisitor::fromNode($node)->templates), + templateNames: PhpDocParsingVisitor::fromNode($node)->templateNames(), ); return null; diff --git a/src/Reflector/PhpParserReflector.php b/src/Reflector/PhpParserReflector.php index 0115aae..cddbcaf 100644 --- a/src/Reflector/PhpParserReflector.php +++ b/src/Reflector/PhpParserReflector.php @@ -112,7 +112,7 @@ private function reflectParent(Stmt\ClassLike $node, PhpDoc $phpDoc): ?Type\Name $parentClass = $this->nameContext->resolveNameAsClass($node->extends->toCodeString()); - foreach ($phpDoc->extendedTypes as $phpDocExtendedType) { + foreach ($phpDoc->extendedTypes() as $phpDocExtendedType) { /** @var Type\NamedObjectType $extendedType */ $extendedType = $this->safelyReflectPhpDocType($phpDocExtendedType); @@ -131,17 +131,17 @@ private function reflectOwnInterfaceTypes(Stmt\ClassLike $node, PhpDoc $phpDoc): { if ($node instanceof Stmt\Interface_) { $interfaceNames = $node->extends; - $phpDocInterfaceTypes = $phpDoc->extendedTypes; + $phpDocInterfaceTypes = $phpDoc->extendedTypes(); } elseif ($node instanceof Stmt\Class_) { $interfaceNames = $node->implements; - $phpDocInterfaceTypes = $phpDoc->implementedTypes; + $phpDocInterfaceTypes = $phpDoc->implementedTypes(); } elseif ($node instanceof Stmt\Enum_) { $interfaceNames = [ ...$node->implements, new Name\FullyQualified(\UnitEnum::class), ...($node->scalarType === null ? [] : [new Name\FullyQualified(\BackedEnum::class)]), ]; - $phpDocInterfaceTypes = $phpDoc->implementedTypes; + $phpDocInterfaceTypes = $phpDoc->implementedTypes(); } else { return []; } @@ -194,7 +194,7 @@ private function reflectOwnProperties(string $class, Stmt\ClassLike $classNode): foreach ($classNode->getProperties() as $node) { $phpDoc = PhpDocParsingVisitor::fromNode($node); - $type = $this->reflectType($node->type, $phpDoc->varType); + $type = $this->reflectType($node->type, $phpDoc->varType()); foreach ($node->props as $property) { $properties[] = new PropertyReflection( @@ -235,7 +235,7 @@ class: $class, hasDefaultValue: $node->default !== null || $node->type === null, promoted: true, modifiers: $modifiers, - type: $this->reflectType($node->type, $phpDoc->paramTypes[$name] ?? null), + type: $this->reflectType($node->type, $phpDoc->paramTypes()[$name] ?? null), startLine: $node->getStartLine() > 0 ? $node->getStartLine() : null, endLine: $node->getEndLine() > 0 ? $node->getEndLine() : null, ); @@ -294,7 +294,7 @@ private function reflectOwnMethods(string $class, Stmt\ClassLike $classNode): ar $phpDoc = PhpDocParsingVisitor::fromNode($node); try { - $this->nameContext->enterMethod($name, array_keys($phpDoc->templates)); + $this->nameContext->enterMethod($name, $phpDoc->templateNames()); $methods[] = new MethodReflection( class: $class, @@ -310,7 +310,7 @@ class: $class, returnsReference: $node->byRef, generator: $this->reflectIsGenerator($node), parameters: $this->reflectParameters([$class, $name], $node->params, $phpDoc), - returnType: $this->reflectType($node->returnType, $phpDoc->returnType), + returnType: $this->reflectType($node->returnType, $phpDoc->returnType()), ); } finally { $this->nameContext->leaveMethod(); @@ -389,7 +389,7 @@ function: $function, optional: $isOptional, variadic: $node->variadic, promoted: $this->isParameterPromoted($node), - type: $this->reflectType($node->type, $phpDoc->paramTypes[$name] ?? null), + type: $this->reflectType($node->type, $phpDoc->paramTypes()[$name] ?? null), startLine: $node->getStartLine() > 0 ? $node->getStartLine() : null, endLine: $node->getEndLine() > 0 ? $node->getEndLine() : null, ); @@ -405,7 +405,7 @@ private function reflectTemplates(PhpDoc $phpDoc): array { $templates = []; - foreach (array_values($phpDoc->templates) as $position => $template) { + foreach ($phpDoc->templates() as $position => $template) { $variance = $template->getAttribute('variance'); $templates[] = new TemplateReflection( position: $position, diff --git a/tests/unit/PhpDocParser/PhpDocParserTest.php b/tests/unit/PhpDocParser/PhpDocParserTest.php index 04f4116..080c5bc 100644 --- a/tests/unit/PhpDocParser/PhpDocParserTest.php +++ b/tests/unit/PhpDocParser/PhpDocParserTest.php @@ -14,7 +14,6 @@ use Typhoon\Reflection\Variance; #[CoversClass(PhpDocParser::class)] -#[CoversClass(PhpDocBuilder::class)] #[CoversClass(PhpDoc::class)] final class PhpDocParserTest extends TestCase { @@ -28,7 +27,7 @@ public function testItReturnsNullVarTypeWhenNoVarTag(): void * @example */ PHP, - )->varType; + )->varType(); self::assertNull($varType); } @@ -46,7 +45,7 @@ public function testItReturnsLatestPrioritizedVarTagType(): void * @psalm-var string */ PHP, - )->varType; + )->varType(); self::assertEquals(new IdentifierTypeNode('string'), $varType); } @@ -61,7 +60,7 @@ public function testItReturnsNullParamTypeWhenNoParamTag(): void * @example */ PHP, - )->paramTypes; + )->paramTypes(); self::assertEmpty($paramTypes); } @@ -81,7 +80,7 @@ public function testItReturnsLatestPrioritizedParamTagType(): void * @psalm-param string $a */ PHP, - )->paramTypes; + )->paramTypes(); self::assertEquals( [ @@ -102,7 +101,7 @@ public function testItReturnsNullReturnTypeWhenNoReturnTag(): void * @example */ PHP, - )->returnType; + )->returnType(); self::assertNull($returnType); } @@ -120,7 +119,7 @@ public function testItReturnsLatestPrioritizedReturnTagType(): void * @psalm-return string */ PHP, - )->returnType; + )->returnType(); self::assertEquals(new IdentifierTypeNode('string'), $returnType); } @@ -135,11 +134,26 @@ public function testItReturnsEmptyTemplatesWhenNoTemplateTag(): void * @example */ PHP, - )->templates; + )->templates(); self::assertEmpty($templates); } + public function testItReturnsEmptyTemplateNamesWhenNoTemplateTag(): void + { + $parser = new PhpDocParser(); + + $templateNames = $parser->parsePhpDoc( + <<<'PHP' + /** + * @example + */ + PHP, + )->templateNames(); + + self::assertEmpty($templateNames); + } + public function testItReturnsLatestPrioritizedTemplates(): void { $parser = new PhpDocParser(); @@ -155,17 +169,37 @@ public function testItReturnsLatestPrioritizedTemplates(): void * @psalm-template T of string */ PHP, - )->templates; + )->templates(); self::assertEquals( [ - 'T' => $this->createTemplateTagValueNode('T', new IdentifierTypeNode('string'), Variance::INVARIANT), - 'T2' => $this->createTemplateTagValueNode('T2', new IdentifierTypeNode('mixed'), Variance::INVARIANT), + $this->createTemplateTagValueNode('T', new IdentifierTypeNode('string'), Variance::INVARIANT), + $this->createTemplateTagValueNode('T2', new IdentifierTypeNode('mixed'), Variance::INVARIANT), ], $templates, ); } + public function testItReturnsLatestPrioritizedTemplateNames(): void + { + $parser = new PhpDocParser(); + + $templateNames = $parser->parsePhpDoc( + <<<'PHP' + /** + * @example + * @template T of int + * @template T2 of object + * @template T2 of mixed + * @psalm-template T of float + * @psalm-template T of string + */ + PHP, + )->templateNames(); + + self::assertSame(['T', 'T2'], $templateNames); + } + public function testItAddsVarianceAttributeToTemplates(): void { $parser = new PhpDocParser(); @@ -178,31 +212,31 @@ public function testItAddsVarianceAttributeToTemplates(): void * @template-contravariant TContravariant */ PHP, - )->templates; + )->templates(); self::assertEquals( [ - 'TInvariant' => $this->createTemplateTagValueNode('TInvariant', null, Variance::INVARIANT), - 'TCovariant' => $this->createTemplateTagValueNode('TCovariant', null, Variance::COVARIANT), - 'TContravariant' => $this->createTemplateTagValueNode('TContravariant', null, Variance::CONTRAVARIANT), + $this->createTemplateTagValueNode('TInvariant', null, Variance::INVARIANT), + $this->createTemplateTagValueNode('TCovariant', null, Variance::COVARIANT), + $this->createTemplateTagValueNode('TContravariant', null, Variance::CONTRAVARIANT), ], $templates, ); } - public function testItReturnsEmptyInheritedTypesWhenNoExtendsTag(): void + public function testItReturnsEmptyExtendedTypesWhenNoExtendsTag(): void { $parser = new PhpDocParser(); - $inheritedTypes = $parser->parsePhpDoc( + $extendedTypes = $parser->parsePhpDoc( <<<'PHP' /** * @example */ PHP, - )->extendedTypes; + )->extendedTypes(); - self::assertEmpty($inheritedTypes); + self::assertEmpty($extendedTypes); } public function testItReturnsLatestPrioritizedExtendedTypes(): void @@ -221,7 +255,7 @@ public function testItReturnsLatestPrioritizedExtendedTypes(): void * @phpstan-extends C */ PHP, - )->extendedTypes; + )->extendedTypes(); self::assertEquals( [ @@ -232,26 +266,26 @@ public function testItReturnsLatestPrioritizedExtendedTypes(): void ); } - public function testItReturnsEmptyInheritedTypesWhenNoImplementsTag(): void + public function testItReturnsEmptyImplementedTypesWhenNoImplementsTag(): void { $parser = new PhpDocParser(); - $inheritedTypes = $parser->parsePhpDoc( + $implementedTypes = $parser->parsePhpDoc( <<<'PHP' /** * @example */ PHP, - )->implementedTypes; + )->implementedTypes(); - self::assertEmpty($inheritedTypes); + self::assertEmpty($implementedTypes); } public function testItReturnsLatestPrioritizedImplementedTypes(): void { $parser = new PhpDocParser(); - $implemented = $parser->parsePhpDoc( + $implementedTypes = $parser->parsePhpDoc( <<<'PHP' /** * @example @@ -263,14 +297,14 @@ public function testItReturnsLatestPrioritizedImplementedTypes(): void * @phpstan-implements C */ PHP, - )->implementedTypes; + )->implementedTypes(); self::assertEquals( [ $this->createGenericTypeNode(new IdentifierTypeNode('C'), [new IdentifierTypeNode('string')]), $this->createGenericTypeNode(new IdentifierTypeNode('D'), [new IdentifierTypeNode('mixed')]), ], - $implemented, + $implementedTypes, ); } @@ -288,7 +322,7 @@ public function testItCachesPriority(): void * @param string $a */ PHP, - ); + )->paramTypes(); } private function createTemplateTagValueNode(string $name, ?TypeNode $bound, Variance $variance): TemplateTagValueNode