33namespace PHPStan \PhpDoc ;
44
55use PHPStan \Analyser \NameScope ;
6+ use PHPStan \PhpDoc \Tag \DeprecatedTag ;
67use PHPStan \PhpDoc \Tag \MixinTag ;
8+ use PHPStan \PhpDoc \Tag \ParamTag ;
9+ use PHPStan \PhpDoc \Tag \ReturnTag ;
710use PHPStan \PhpDoc \Tag \ThrowsTag ;
811use PHPStan \PhpDoc \Tag \TypedTag ;
12+ use PHPStan \PhpDoc \Tag \VarTag ;
913use PHPStan \PhpDocParser \Ast \PhpDoc \PhpDocNode ;
1014use PHPStan \Type \Generic \TemplateTypeHelper ;
1115use 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 (),
0 commit comments