Skip to content

Commit f6fd473

Browse files
committed
Get rid of cloning in ResolvedPhpDocBlock
1 parent f683b34 commit f6fd473

8 files changed

Lines changed: 272 additions & 88 deletions

src/PhpDoc/PhpDocInheritanceResolver.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ private function docBlockTreeToResolvedDocBlock(PhpDocBlock $phpDocBlock, ?strin
8787
}
8888

8989
$oneResolvedDockBlock = $this->docBlockToResolvedDocBlock($phpDocBlock, $traitName, $functionName);
90-
return $oneResolvedDockBlock->cloneAndMerge($parents, $parentPhpDocBlocks);
90+
return $oneResolvedDockBlock->merge($parents, $parentPhpDocBlocks);
9191
}
9292

9393
private function docBlockToResolvedDocBlock(PhpDocBlock $phpDocBlock, ?string $traitName, ?string $functionName): ResolvedPhpDocBlock

src/PhpDoc/ResolvedPhpDocBlock.php

Lines changed: 125 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@
33
namespace PHPStan\PhpDoc;
44

55
use PHPStan\Analyser\NameScope;
6+
use PHPStan\PhpDoc\Tag\DeprecatedTag;
67
use PHPStan\PhpDoc\Tag\MixinTag;
8+
use PHPStan\PhpDoc\Tag\ParamTag;
9+
use PHPStan\PhpDoc\Tag\ReturnTag;
710
use PHPStan\PhpDoc\Tag\ThrowsTag;
811
use PHPStan\PhpDoc\Tag\TypedTag;
12+
use PHPStan\PhpDoc\Tag\VarTag;
913
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
1014
use PHPStan\Type\Generic\TemplateTypeHelper;
1115
use PHPStan\Type\Generic\TemplateTypeMap;
@@ -101,6 +105,7 @@ public static function create(
101105
PhpDocNodeResolver $phpDocNodeResolver
102106
): self
103107
{
108+
// new property also needs to be added to createEmpty() and merge()
104109
$self = new self();
105110
$self->phpDocNode = $phpDocNode;
106111
$self->phpDocString = $phpDocString;
@@ -115,8 +120,8 @@ public static function create(
115120

116121
public static function createEmpty(): self
117122
{
123+
// new property also needs to be added to merge()
118124
$self = new self();
119-
$self->phpDocNode = new PhpDocNode([]);
120125
$self->phpDocString = '/** */';
121126
$self->filename = null;
122127
$self->templateTypeMap = TemplateTypeMap::createEmpty();
@@ -139,6 +144,40 @@ public static function createEmpty(): self
139144
return $self;
140145
}
141146

147+
/**
148+
* @param array<int, self> $parents
149+
* @param array<int, PhpDocBlock> $parentPhpDocBlocks
150+
* @return self
151+
*/
152+
public function merge(array $parents, array $parentPhpDocBlocks): self
153+
{
154+
// new property also needs to be added to createEmpty()
155+
$result = new self();
156+
// we will resolve everything on $this here so these properties don't have to be populated
157+
// skip $result->phpDocNode
158+
// skip $result->phpDocString - just for stubs
159+
$result->filename = $this->filename;
160+
// skip $result->nameScope
161+
$result->templateTypeMap = $this->templateTypeMap;
162+
$result->templateTags = $this->templateTags;
163+
// skip $result->phpDocNodeResolver
164+
$result->varTags = self::mergeVarTags($this->getVarTags(), $parents, $parentPhpDocBlocks);
165+
$result->methodTags = $this->getMethodTags();
166+
$result->propertyTags = $this->getPropertyTags();
167+
$result->extendsTags = $this->getExtendsTags();
168+
$result->implementsTags = $this->getImplementsTags();
169+
$result->usesTags = $this->getUsesTags();
170+
$result->paramTags = self::mergeParamTags($this->getParamTags(), $parents, $parentPhpDocBlocks);
171+
$result->returnTag = self::mergeReturnTags($this->getReturnTag(), $parents, $parentPhpDocBlocks);
172+
$result->throwsTag = self::mergeThrowsTags($this->getThrowsTag(), $parents);
173+
$result->mixinTags = $this->getMixinTags();
174+
$result->deprecatedTag = self::mergeDeprecatedTags($this->getDeprecatedTag(), $parents);
175+
$result->isDeprecated = $result->deprecatedTag !== null;
176+
$result->isInternal = $this->isInternal();
177+
$result->isFinal = $this->isFinal();
178+
return $result;
179+
}
180+
142181
/**
143182
* @param array<string, string> $parameterNameMapping
144183
* @return self
@@ -180,11 +219,6 @@ public function changeParameterNamesByMapping(array $parameterNameMapping): self
180219
return $self;
181220
}
182221

183-
public function getPhpDocNode(): PhpDocNode
184-
{
185-
return $this->phpDocNode;
186-
}
187-
188222
public function getPhpDocString(): string
189223
{
190224
return $this->phpDocString;
@@ -385,162 +419,174 @@ public function getTemplateTypeMap(): TemplateTypeMap
385419
}
386420

387421
/**
422+
* @param array<string|int, VarTag> $varTags
388423
* @param array<int, self> $parents
389424
* @param array<int, PhpDocBlock> $parentPhpDocBlocks
390-
* @return self
391-
*/
392-
public function cloneAndMerge(array $parents, array $parentPhpDocBlocks): self
393-
{
394-
$result = clone $this;
395-
$result->mergeTags($parents, $parentPhpDocBlocks);
396-
return $result;
397-
}
398-
399-
/**
400-
* @param array<int, self> $parents
401-
* @param array<int, PhpDocBlock> $parentPhpDocBlocks
425+
* @return array<string|int, VarTag>
402426
*/
403-
private function mergeTags(array $parents, array $parentPhpDocBlocks): void // phpcs:disable
404-
{
405-
$this->mergeVarTags($parents, $parentPhpDocBlocks);
406-
$this->mergeParamTags($parents, $parentPhpDocBlocks);
407-
$this->mergeReturnTags($parents, $parentPhpDocBlocks);
408-
$this->mergeThrowsTags($parents);
409-
$this->mergeDeprecatedTags($parents);
410-
}
411-
412-
/**
413-
* @param array<int, self> $parents
414-
* @param array<int, PhpDocBlock> $parentPhpDocBlocks
415-
*/
416-
private function mergeVarTags(array $parents, array $parentPhpDocBlocks): void
427+
private static function mergeVarTags(array $varTags, array $parents, array $parentPhpDocBlocks): array
417428
{
418429
// Only allow one var tag per comment. Check the parent if child does not have this tag.
419-
if (count($this->getVarTags()) > 0) {
420-
return;
430+
if (count($varTags) > 0) {
431+
return $varTags;
421432
}
422433

423434
foreach ($parents as $i => $parent) {
424-
$this->mergeOneParentVarTags($parent, $parentPhpDocBlocks[$i]);
435+
$result = self::mergeOneParentVarTags($parent, $parentPhpDocBlocks[$i]);
436+
if ($result === null) {
437+
continue;
438+
}
439+
440+
return $result;
425441
}
442+
443+
return [];
426444
}
427445

428-
private function mergeOneParentVarTags(self $parent, PhpDocBlock $phpDocBlock): void
446+
/**
447+
* @param ResolvedPhpDocBlock $parent
448+
* @param PhpDocBlock $phpDocBlock
449+
* @return array<string|int, VarTag>|null
450+
*/
451+
private static function mergeOneParentVarTags(self $parent, PhpDocBlock $phpDocBlock): ?array
429452
{
430453
foreach ($parent->getVarTags() as $key => $parentVarTag) {
431-
$this->varTags = [$key => $this->resolveTemplateTypeInTag($parentVarTag, $phpDocBlock)];
432-
break;
454+
return [$key => self::resolveTemplateTypeInTag($parentVarTag, $phpDocBlock)];
433455
}
456+
457+
return null;
434458
}
435459

436460
/**
461+
* @param array<string, ParamTag> $paramTags
437462
* @param array<int, self> $parents
438463
* @param array<int, PhpDocBlock> $parentPhpDocBlocks
464+
* @return array<string, ParamTag>
439465
*/
440-
private function mergeParamTags(array $parents, array $parentPhpDocBlocks): void
466+
private static function mergeParamTags(array $paramTags, array $parents, array $parentPhpDocBlocks): array
441467
{
442-
$this->getParamTags();
443-
444468
foreach ($parents as $i => $parent) {
445-
$this->mergeOneParentParamTags($parent, $parentPhpDocBlocks[$i]);
469+
$paramTags = self::mergeOneParentParamTags($paramTags, $parent, $parentPhpDocBlocks[$i]);
446470
}
471+
472+
return $paramTags;
447473
}
448474

449-
private function mergeOneParentParamTags(self $parent, PhpDocBlock $phpDocBlock): void
475+
/**
476+
* @param array<string, ParamTag> $paramTags
477+
* @param ResolvedPhpDocBlock $parent
478+
* @param PhpDocBlock $phpDocBlock
479+
* @return array<string, ParamTag>
480+
*/
481+
private static function mergeOneParentParamTags(array $paramTags, self $parent, PhpDocBlock $phpDocBlock): array
450482
{
451483
$parentParamTags = $phpDocBlock->transformArrayKeysWithParameterNameMapping($parent->getParamTags());
452484

453485
foreach ($parentParamTags as $name => $parentParamTag) {
454-
if ($this->paramTags === false || array_key_exists($name, $this->paramTags)) {
486+
if (array_key_exists($name, $paramTags)) {
455487
continue;
456488
}
457489

458-
$this->paramTags[$name] = $this->resolveTemplateTypeInTag($parentParamTag, $phpDocBlock);
490+
$paramTags[$name] = self::resolveTemplateTypeInTag($parentParamTag, $phpDocBlock);
459491
}
492+
493+
return $paramTags;
460494
}
461495

462496
/**
497+
* @param ReturnTag|null $returnTag
463498
* @param array<int, self> $parents
464499
* @param array<int, PhpDocBlock> $parentPhpDocBlocks
500+
* @return ReturnTag|Null
465501
*/
466-
private function mergeReturnTags(array $parents, array $parentPhpDocBlocks): void
502+
private static function mergeReturnTags(?ReturnTag $returnTag, array $parents, array $parentPhpDocBlocks): ?ReturnTag
467503
{
468-
if ($this->getReturnTag() !== null) {
469-
return;
504+
if ($returnTag !== null) {
505+
return $returnTag;
470506
}
471507

472508
foreach ($parents as $i => $parent) {
473-
$this->mergeOneParentReturnTag($parent, $parentPhpDocBlocks[$i]);
509+
$result = self::mergeOneParentReturnTag($returnTag, $parent, $parentPhpDocBlocks[$i]);
510+
if ($result === null) {
511+
continue;
512+
}
513+
514+
return $result;
474515
}
516+
517+
return null;
475518
}
476519

477-
private function mergeOneParentReturnTag(self $parent, PhpDocBlock $phpDocBlock): void
520+
private static function mergeOneParentReturnTag(?ReturnTag $returnTag, self $parent, PhpDocBlock $phpDocBlock): ?ReturnTag
478521
{
479522
$parentReturnTag = $parent->getReturnTag();
480523
if ($parentReturnTag === null) {
481-
return;
524+
return $returnTag;
482525
}
483526

484527
$parentType = $parentReturnTag->getType();
485528

486529
// Each parent would overwrite the previous one except if it returns a less specific type.
487530
// Do not care for incompatible types as there is a separate rule for that.
488-
if ($this->returnTag !== null && $this->returnTag !== false && $parentType->isSuperTypeOf($this->returnTag->getType())->yes()) {
489-
return;
531+
if ($returnTag !== null && $parentType->isSuperTypeOf($returnTag->getType())->yes()) {
532+
return null;
490533
}
491534

492-
$this->returnTag = $this->resolveTemplateTypeInTag($parentReturnTag->cloneImplicit(), $phpDocBlock);
535+
return self::resolveTemplateTypeInTag($parentReturnTag->toImplicit(), $phpDocBlock);
493536
}
494537

495538
/**
496539
* @param array<int, self> $parents
497540
*/
498-
private function mergeThrowsTags(array $parents): void
541+
private static function mergeThrowsTags(?ThrowsTag $throwsTag, array $parents): ?ThrowsTag
499542
{
500-
$this->getThrowsTag();
501-
502543
foreach ($parents as $parent) {
503-
$this->mergeOneParentThrowsTag($parent);
544+
$result = self::mergeOneParentThrowsTag($throwsTag, $parent);
545+
if ($result === null) {
546+
continue;
547+
}
548+
549+
$throwsTag = $result;
504550
}
551+
552+
return $throwsTag;
505553
}
506554

507-
private function mergeOneParentThrowsTag(self $parent): void
555+
private static function mergeOneParentThrowsTag(?ThrowsTag $throwsTag, self $parent): ?ThrowsTag
508556
{
509557
$parentThrowsTag = $parent->getThrowsTag();
510558
if ($parentThrowsTag === null) {
511-
return;
559+
return $throwsTag;
512560
}
513561

514-
if ($this->throwsTag === null || $this->throwsTag === false) {
515-
$this->throwsTag = $parentThrowsTag;
516-
} else {
517-
$type = TypeCombinator::union($this->throwsTag->getType(), $parentThrowsTag->getType());
518-
$this->throwsTag = new ThrowsTag($type);
562+
if ($throwsTag === null) {
563+
return $parentThrowsTag;
519564
}
565+
566+
return new ThrowsTag(
567+
TypeCombinator::union($throwsTag->getType(), $parentThrowsTag->getType())
568+
);
520569
}
521570

522571
/**
523572
* @param array<int, self> $parents
524573
*/
525-
private function mergeDeprecatedTags(array $parents): void
574+
private static function mergeDeprecatedTags(?DeprecatedTag $deprecatedTag, array $parents): ?DeprecatedTag
526575
{
527-
$this->getDeprecatedTag();
528-
529-
foreach ($parents as $parent) {
530-
$this->mergeOneParentDeprecatedTag($parent);
576+
if ($deprecatedTag !== null) {
577+
return $deprecatedTag;
531578
}
532579

533-
$this->isDeprecated = ($this->deprecatedTag !== null);
534-
}
580+
foreach ($parents as $parent) {
581+
$result = $parent->getDeprecatedTag();
582+
if ($result === null) {
583+
continue;
584+
}
535585

536-
private function mergeOneParentDeprecatedTag(self $parent): void
537-
{
538-
$parentDeprecatedTag = $parent->getDeprecatedTag();
539-
if ($parentDeprecatedTag === null) {
540-
return;
586+
return $result;
541587
}
542588

543-
$this->deprecatedTag = $parentDeprecatedTag;
589+
return null;
544590
}
545591

546592
/**
@@ -549,7 +595,7 @@ private function mergeOneParentDeprecatedTag(self $parent): void
549595
* @param PhpDocBlock $phpDocBlock
550596
* @return T
551597
*/
552-
private function resolveTemplateTypeInTag(TypedTag $tag, PhpDocBlock $phpDocBlock): TypedTag
598+
private static function resolveTemplateTypeInTag(TypedTag $tag, PhpDocBlock $phpDocBlock): TypedTag
553599
{
554600
$type = TemplateTypeHelper::resolveTemplateTypes(
555601
$tag->getType(),

src/PhpDoc/Tag/ReturnTag.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public function withType(Type $type): TypedTag
3838
return new self($type, $this->isExplicit);
3939
}
4040

41-
public function cloneImplicit(): self
41+
public function toImplicit(): self
4242
{
4343
return new self($this->type, false);
4444
}

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9874,7 +9874,8 @@ public function dataInheritPhpDocMerging(): array
98749874
return array_merge(
98759875
$this->gatherAssertTypes(__DIR__ . '/data/inherit-phpdoc-merging-var.php'),
98769876
$this->gatherAssertTypes(__DIR__ . '/data/inherit-phpdoc-merging-param.php'),
9877-
$this->gatherAssertTypes(__DIR__ . '/data/inherit-phpdoc-merging-return.php')
9877+
$this->gatherAssertTypes(__DIR__ . '/data/inherit-phpdoc-merging-return.php'),
9878+
$this->gatherAssertTypes(__DIR__ . '/data/inherit-phpdoc-merging-template.php')
98789879
);
98799880
}
98809881

tests/PHPStan/Analyser/data/inherit-phpdoc-merging-return.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,11 @@ public function method() { }
4848

4949

5050
function (ParentClass $foo): void {
51-
assertType('InheritDocMergingReturn\C', $foo->method());
51+
assertType('InheritDocMergingReturn\B', $foo->method());
5252
};
5353

5454
function (ChildClass $foo): void {
55-
assertType('InheritDocMergingReturn\C', $foo->method());
55+
assertType('InheritDocMergingReturn\B', $foo->method());
5656
};
5757

5858
function (ChildClass2 $foo): void {

0 commit comments

Comments
 (0)