1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\PhpDoc;
4:
5: use PHPStan\Analyser\NameScope;
6: use PHPStan\PhpDoc\Tag\AssertTag;
7: use PHPStan\PhpDoc\Tag\DeprecatedTag;
8: use PHPStan\PhpDoc\Tag\ExtendsTag;
9: use PHPStan\PhpDoc\Tag\ImplementsTag;
10: use PHPStan\PhpDoc\Tag\MethodTag;
11: use PHPStan\PhpDoc\Tag\MixinTag;
12: use PHPStan\PhpDoc\Tag\ParamClosureThisTag;
13: use PHPStan\PhpDoc\Tag\ParamOutTag;
14: use PHPStan\PhpDoc\Tag\ParamTag;
15: use PHPStan\PhpDoc\Tag\PropertyTag;
16: use PHPStan\PhpDoc\Tag\RequireExtendsTag;
17: use PHPStan\PhpDoc\Tag\RequireImplementsTag;
18: use PHPStan\PhpDoc\Tag\ReturnTag;
19: use PHPStan\PhpDoc\Tag\SelfOutTypeTag;
20: use PHPStan\PhpDoc\Tag\TemplateTag;
21: use PHPStan\PhpDoc\Tag\ThrowsTag;
22: use PHPStan\PhpDoc\Tag\TypeAliasImportTag;
23: use PHPStan\PhpDoc\Tag\TypeAliasTag;
24: use PHPStan\PhpDoc\Tag\TypedTag;
25: use PHPStan\PhpDoc\Tag\UsesTag;
26: use PHPStan\PhpDoc\Tag\VarTag;
27: use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
28: use PHPStan\Reflection\ClassReflection;
29: use PHPStan\Reflection\ReflectionProvider;
30: use PHPStan\Type\ConditionalTypeForParameter;
31: use PHPStan\Type\Generic\TemplateTypeHelper;
32: use PHPStan\Type\Generic\TemplateTypeMap;
33: use PHPStan\Type\Generic\TemplateTypeVariance;
34: use PHPStan\Type\StaticType;
35: use PHPStan\Type\Type;
36: use PHPStan\Type\TypeTraverser;
37: use function array_key_exists;
38: use function array_map;
39: use function count;
40: use function is_bool;
41: use function substr;
42:
43: /**
44: * @api
45: * @final
46: */
47: class ResolvedPhpDocBlock
48: {
49:
50: public const EMPTY_DOC_STRING = '/** */';
51:
52: private PhpDocNode $phpDocNode;
53:
54: /** @var PhpDocNode[] */
55: private array $phpDocNodes;
56:
57: private string $phpDocString;
58:
59: private ?string $filename;
60:
61: private ?NameScope $nameScope = null;
62:
63: private TemplateTypeMap $templateTypeMap;
64:
65: /** @var array<string, TemplateTag> */
66: private array $templateTags;
67:
68: private PhpDocNodeResolver $phpDocNodeResolver;
69:
70: private ReflectionProvider $reflectionProvider;
71:
72: /** @var array<(string|int), VarTag>|false */
73: private array|false $varTags = false;
74:
75: /** @var array<string, MethodTag>|false */
76: private array|false $methodTags = false;
77:
78: /** @var array<string, PropertyTag>|false */
79: private array|false $propertyTags = false;
80:
81: /** @var array<string, ExtendsTag>|false */
82: private array|false $extendsTags = false;
83:
84: /** @var array<string, ImplementsTag>|false */
85: private array|false $implementsTags = false;
86:
87: /** @var array<string, UsesTag>|false */
88: private array|false $usesTags = false;
89:
90: /** @var array<string, ParamTag>|false */
91: private array|false $paramTags = false;
92:
93: /** @var array<string, ParamOutTag>|false */
94: private array|false $paramOutTags = false;
95:
96: /** @var array<string, bool>|false */
97: private array|false $paramsImmediatelyInvokedCallable = false;
98:
99: /** @var array<string, ParamClosureThisTag>|false */
100: private array|false $paramClosureThisTags = false;
101:
102: private ReturnTag|false|null $returnTag = false;
103:
104: private ThrowsTag|false|null $throwsTag = false;
105:
106: /** @var array<MixinTag>|false */
107: private array|false $mixinTags = false;
108:
109: /** @var array<RequireExtendsTag>|false */
110: private array|false $requireExtendsTags = false;
111:
112: /** @var array<RequireImplementsTag>|false */
113: private array|false $requireImplementsTags = false;
114:
115: /** @var array<TypeAliasTag>|false */
116: private array|false $typeAliasTags = false;
117:
118: /** @var array<TypeAliasImportTag>|false */
119: private array|false $typeAliasImportTags = false;
120:
121: /** @var array<AssertTag>|false */
122: private array|false $assertTags = false;
123:
124: private SelfOutTypeTag|false|null $selfOutTypeTag = false;
125:
126: private DeprecatedTag|false|null $deprecatedTag = false;
127:
128: private ?bool $isDeprecated = null;
129:
130: private ?bool $isNotDeprecated = null;
131:
132: private ?bool $isInternal = null;
133:
134: private ?bool $isFinal = null;
135:
136: /** @var bool|'notLoaded'|null */
137: private bool|string|null $isPure = 'notLoaded';
138:
139: private ?bool $isReadOnly = null;
140:
141: private ?bool $isImmutable = null;
142:
143: private ?bool $isAllowedPrivateMutation = null;
144:
145: private ?bool $hasConsistentConstructor = null;
146:
147: private ?bool $acceptsNamedArguments = null;
148:
149: private function __construct()
150: {
151: }
152:
153: /**
154: * @param TemplateTag[] $templateTags
155: */
156: public static function create(
157: PhpDocNode $phpDocNode,
158: string $phpDocString,
159: ?string $filename,
160: NameScope $nameScope,
161: TemplateTypeMap $templateTypeMap,
162: array $templateTags,
163: PhpDocNodeResolver $phpDocNodeResolver,
164: ReflectionProvider $reflectionProvider,
165: ): self
166: {
167: // new property also needs to be added to withNameScope(), createEmpty() and merge()
168: $self = new self();
169: $self->phpDocNode = $phpDocNode;
170: $self->phpDocNodes = [$phpDocNode];
171: $self->phpDocString = $phpDocString;
172: $self->filename = $filename;
173: $self->nameScope = $nameScope;
174: $self->templateTypeMap = $templateTypeMap;
175: $self->templateTags = $templateTags;
176: $self->phpDocNodeResolver = $phpDocNodeResolver;
177: $self->reflectionProvider = $reflectionProvider;
178:
179: return $self;
180: }
181:
182: public function withNameScope(NameScope $nameScope): self
183: {
184: $self = new self();
185: $self->phpDocNode = $this->phpDocNode;
186: $self->phpDocNodes = $this->phpDocNodes;
187: $self->phpDocString = $this->phpDocString;
188: $self->filename = $this->filename;
189: $self->nameScope = $nameScope;
190: $self->templateTypeMap = $this->templateTypeMap;
191: $self->templateTags = $this->templateTags;
192: $self->phpDocNodeResolver = $this->phpDocNodeResolver;
193: $self->reflectionProvider = $this->reflectionProvider;
194:
195: return $self;
196: }
197:
198: public static function createEmpty(): self
199: {
200: // new property also needs to be added to merge()
201: $self = new self();
202: $self->phpDocString = self::EMPTY_DOC_STRING;
203: $self->phpDocNodes = [];
204: $self->filename = null;
205: $self->templateTypeMap = TemplateTypeMap::createEmpty();
206: $self->templateTags = [];
207: $self->varTags = [];
208: $self->methodTags = [];
209: $self->propertyTags = [];
210: $self->extendsTags = [];
211: $self->implementsTags = [];
212: $self->usesTags = [];
213: $self->paramTags = [];
214: $self->paramOutTags = [];
215: $self->paramsImmediatelyInvokedCallable = [];
216: $self->paramClosureThisTags = [];
217: $self->returnTag = null;
218: $self->throwsTag = null;
219: $self->mixinTags = [];
220: $self->requireExtendsTags = [];
221: $self->requireImplementsTags = [];
222: $self->typeAliasTags = [];
223: $self->typeAliasImportTags = [];
224: $self->assertTags = [];
225: $self->selfOutTypeTag = null;
226: $self->deprecatedTag = null;
227: $self->isDeprecated = false;
228: $self->isNotDeprecated = false;
229: $self->isInternal = false;
230: $self->isFinal = false;
231: $self->isPure = null;
232: $self->isReadOnly = false;
233: $self->isImmutable = false;
234: $self->isAllowedPrivateMutation = false;
235: $self->hasConsistentConstructor = false;
236: $self->acceptsNamedArguments = true;
237:
238: return $self;
239: }
240:
241: /**
242: * @param array<int, self> $parents
243: * @param array<int, PhpDocBlock> $parentPhpDocBlocks
244: */
245: public function merge(array $parents, array $parentPhpDocBlocks): self
246: {
247: $className = $this->nameScope !== null ? $this->nameScope->getClassName() : null;
248: $classReflection = $className !== null && $this->reflectionProvider->hasClass($className)
249: ? $this->reflectionProvider->getClass($className)
250: : null;
251:
252: // new property also needs to be added to createEmpty()
253: $result = new self();
254: // we will resolve everything on $this here so these properties don't have to be populated
255: // skip $result->phpDocNode
256: $phpDocNodes = $this->phpDocNodes;
257: $acceptsNamedArguments = $this->acceptsNamedArguments();
258: foreach ($parents as $parent) {
259: foreach ($parent->phpDocNodes as $phpDocNode) {
260: $phpDocNodes[] = $phpDocNode;
261: $acceptsNamedArguments = $acceptsNamedArguments && $parent->acceptsNamedArguments();
262: }
263: }
264: $result->phpDocNodes = $phpDocNodes;
265: $result->phpDocString = $this->phpDocString;
266: $result->filename = $this->filename;
267: // skip $result->nameScope
268: $result->templateTypeMap = $this->templateTypeMap;
269: $result->templateTags = $this->templateTags;
270: // skip $result->phpDocNodeResolver
271: $result->varTags = self::mergeVarTags($this->getVarTags(), $parents, $parentPhpDocBlocks);
272: $result->methodTags = $this->getMethodTags();
273: $result->propertyTags = $this->getPropertyTags();
274: $result->extendsTags = $this->getExtendsTags();
275: $result->implementsTags = $this->getImplementsTags();
276: $result->usesTags = $this->getUsesTags();
277: $result->paramTags = self::mergeParamTags($this->getParamTags(), $parents, $parentPhpDocBlocks);
278: $result->paramOutTags = self::mergeParamOutTags($this->getParamOutTags(), $parents, $parentPhpDocBlocks);
279: $result->paramsImmediatelyInvokedCallable = self::mergeParamsImmediatelyInvokedCallable($this->getParamsImmediatelyInvokedCallable(), $parents, $parentPhpDocBlocks);
280: $result->paramClosureThisTags = self::mergeParamClosureThisTags($this->getParamClosureThisTags(), $parents, $parentPhpDocBlocks);
281: $result->returnTag = self::mergeReturnTags($this->getReturnTag(), $classReflection, $parents, $parentPhpDocBlocks);
282: $result->throwsTag = self::mergeThrowsTags($this->getThrowsTag(), $parents);
283: $result->mixinTags = $this->getMixinTags();
284: $result->requireExtendsTags = $this->getRequireExtendsTags();
285: $result->requireImplementsTags = $this->getRequireImplementsTags();
286: $result->typeAliasTags = $this->getTypeAliasTags();
287: $result->typeAliasImportTags = $this->getTypeAliasImportTags();
288: $result->assertTags = self::mergeAssertTags($this->getAssertTags(), $parents, $parentPhpDocBlocks);
289: $result->selfOutTypeTag = self::mergeSelfOutTypeTags($this->getSelfOutTag(), $parents);
290: $result->deprecatedTag = self::mergeDeprecatedTags($this->getDeprecatedTag(), $this->isNotDeprecated(), $parents);
291: $result->isDeprecated = $result->deprecatedTag !== null;
292: $result->isNotDeprecated = $this->isNotDeprecated();
293: $result->isInternal = $this->isInternal();
294: $result->isFinal = $this->isFinal();
295: $result->isPure = self::mergePureTags($this->isPure(), $parents);
296: $result->isReadOnly = $this->isReadOnly();
297: $result->isImmutable = $this->isImmutable();
298: $result->isAllowedPrivateMutation = $this->isAllowedPrivateMutation();
299: $result->hasConsistentConstructor = $this->hasConsistentConstructor();
300: $result->acceptsNamedArguments = $acceptsNamedArguments;
301:
302: return $result;
303: }
304:
305: /**
306: * @param array<string, string> $parameterNameMapping
307: */
308: public function changeParameterNamesByMapping(array $parameterNameMapping): self
309: {
310: if (count($this->phpDocNodes) === 0) {
311: return $this;
312: }
313:
314: $mapParameterCb = static function (Type $type, callable $traverse) use ($parameterNameMapping): Type {
315: if ($type instanceof ConditionalTypeForParameter) {
316: $parameterName = substr($type->getParameterName(), 1);
317: if (array_key_exists($parameterName, $parameterNameMapping)) {
318: $type = $type->changeParameterName('$' . $parameterNameMapping[$parameterName]);
319: }
320: }
321:
322: return $traverse($type);
323: };
324:
325: $newParamTags = [];
326: foreach ($this->getParamTags() as $key => $paramTag) {
327: if (!array_key_exists($key, $parameterNameMapping)) {
328: continue;
329: }
330: $transformedType = TypeTraverser::map($paramTag->getType(), $mapParameterCb);
331: $newParamTags[$parameterNameMapping[$key]] = $paramTag->withType($transformedType);
332: }
333:
334: $newParamOutTags = [];
335: foreach ($this->getParamOutTags() as $key => $paramOutTag) {
336: if (!array_key_exists($key, $parameterNameMapping)) {
337: continue;
338: }
339:
340: $transformedType = TypeTraverser::map($paramOutTag->getType(), $mapParameterCb);
341: $newParamOutTags[$parameterNameMapping[$key]] = $paramOutTag->withType($transformedType);
342: }
343:
344: $newParamsImmediatelyInvokedCallable = [];
345: foreach ($this->getParamsImmediatelyInvokedCallable() as $key => $immediatelyInvokedCallable) {
346: if (!array_key_exists($key, $parameterNameMapping)) {
347: continue;
348: }
349:
350: $newParamsImmediatelyInvokedCallable[$parameterNameMapping[$key]] = $immediatelyInvokedCallable;
351: }
352:
353: $paramClosureThisTags = $this->getParamClosureThisTags();
354: $newParamClosureThisTags = [];
355: foreach ($paramClosureThisTags as $key => $paramClosureThisTag) {
356: if (!array_key_exists($key, $parameterNameMapping)) {
357: continue;
358: }
359:
360: $transformedType = TypeTraverser::map($paramClosureThisTag->getType(), $mapParameterCb);
361: $newParamClosureThisTags[$parameterNameMapping[$key]] = $paramClosureThisTag->withType($transformedType);
362: }
363:
364: $returnTag = $this->getReturnTag();
365: if ($returnTag !== null) {
366: $transformedType = TypeTraverser::map($returnTag->getType(), $mapParameterCb);
367: $returnTag = $returnTag->withType($transformedType);
368: }
369:
370: $assertTags = $this->getAssertTags();
371: if (count($assertTags) > 0) {
372: $assertTags = array_map(static function (AssertTag $tag) use ($parameterNameMapping): AssertTag {
373: $parameterName = substr($tag->getParameter()->getParameterName(), 1);
374: if (array_key_exists($parameterName, $parameterNameMapping)) {
375: $tag = $tag->withParameter($tag->getParameter()->changeParameterName('$' . $parameterNameMapping[$parameterName]));
376: }
377: return $tag;
378: }, $assertTags);
379: }
380:
381: $self = new self();
382: $self->phpDocNode = $this->phpDocNode;
383: $self->phpDocNodes = $this->phpDocNodes;
384: $self->phpDocString = $this->phpDocString;
385: $self->filename = $this->filename;
386: $self->nameScope = $this->nameScope;
387: $self->templateTypeMap = $this->templateTypeMap;
388: $self->templateTags = $this->templateTags;
389: $self->phpDocNodeResolver = $this->phpDocNodeResolver;
390: $self->reflectionProvider = $this->reflectionProvider;
391: $self->varTags = $this->varTags;
392: $self->methodTags = $this->methodTags;
393: $self->propertyTags = $this->propertyTags;
394: $self->extendsTags = $this->extendsTags;
395: $self->implementsTags = $this->implementsTags;
396: $self->usesTags = $this->usesTags;
397: $self->paramTags = $newParamTags;
398: $self->paramOutTags = $newParamOutTags;
399: $self->paramsImmediatelyInvokedCallable = $newParamsImmediatelyInvokedCallable;
400: $self->paramClosureThisTags = $newParamClosureThisTags;
401: $self->returnTag = $returnTag;
402: $self->throwsTag = $this->throwsTag;
403: $self->mixinTags = $this->mixinTags;
404: $self->requireImplementsTags = $this->requireImplementsTags;
405: $self->requireExtendsTags = $this->requireExtendsTags;
406: $self->typeAliasTags = $this->typeAliasTags;
407: $self->typeAliasImportTags = $this->typeAliasImportTags;
408: $self->assertTags = $assertTags;
409: $self->selfOutTypeTag = $this->selfOutTypeTag;
410: $self->deprecatedTag = $this->deprecatedTag;
411: $self->isDeprecated = $this->isDeprecated;
412: $self->isNotDeprecated = $this->isNotDeprecated;
413: $self->isInternal = $this->isInternal;
414: $self->isFinal = $this->isFinal;
415: $self->isPure = $this->isPure;
416:
417: return $self;
418: }
419:
420: public function hasPhpDocString(): bool
421: {
422: return $this->phpDocString !== self::EMPTY_DOC_STRING;
423: }
424:
425: public function getPhpDocString(): string
426: {
427: return $this->phpDocString;
428: }
429:
430: /**
431: * @return PhpDocNode[]
432: */
433: public function getPhpDocNodes(): array
434: {
435: return $this->phpDocNodes;
436: }
437:
438: public function getFilename(): ?string
439: {
440: return $this->filename;
441: }
442:
443: private function getNameScope(): NameScope
444: {
445: return $this->nameScope;
446: }
447:
448: public function getNullableNameScope(): ?NameScope
449: {
450: return $this->nameScope;
451: }
452:
453: /**
454: * @return array<(string|int), VarTag>
455: */
456: public function getVarTags(): array
457: {
458: if ($this->varTags === false) {
459: $this->varTags = $this->phpDocNodeResolver->resolveVarTags(
460: $this->phpDocNode,
461: $this->getNameScope(),
462: );
463: }
464: return $this->varTags;
465: }
466:
467: /**
468: * @return array<string, MethodTag>
469: */
470: public function getMethodTags(): array
471: {
472: if ($this->methodTags === false) {
473: $this->methodTags = $this->phpDocNodeResolver->resolveMethodTags(
474: $this->phpDocNode,
475: $this->getNameScope(),
476: );
477: }
478: return $this->methodTags;
479: }
480:
481: /**
482: * @return array<string, PropertyTag>
483: */
484: public function getPropertyTags(): array
485: {
486: if ($this->propertyTags === false) {
487: $this->propertyTags = $this->phpDocNodeResolver->resolvePropertyTags(
488: $this->phpDocNode,
489: $this->getNameScope(),
490: );
491: }
492: return $this->propertyTags;
493: }
494:
495: /**
496: * @return array<string, TemplateTag>
497: */
498: public function getTemplateTags(): array
499: {
500: return $this->templateTags;
501: }
502:
503: /**
504: * @return array<string, ExtendsTag>
505: */
506: public function getExtendsTags(): array
507: {
508: if ($this->extendsTags === false) {
509: $this->extendsTags = $this->phpDocNodeResolver->resolveExtendsTags(
510: $this->phpDocNode,
511: $this->getNameScope(),
512: );
513: }
514: return $this->extendsTags;
515: }
516:
517: /**
518: * @return array<string, ImplementsTag>
519: */
520: public function getImplementsTags(): array
521: {
522: if ($this->implementsTags === false) {
523: $this->implementsTags = $this->phpDocNodeResolver->resolveImplementsTags(
524: $this->phpDocNode,
525: $this->getNameScope(),
526: );
527: }
528: return $this->implementsTags;
529: }
530:
531: /**
532: * @return array<string, UsesTag>
533: */
534: public function getUsesTags(): array
535: {
536: if ($this->usesTags === false) {
537: $this->usesTags = $this->phpDocNodeResolver->resolveUsesTags(
538: $this->phpDocNode,
539: $this->getNameScope(),
540: );
541: }
542: return $this->usesTags;
543: }
544:
545: /**
546: * @return array<string, ParamTag>
547: */
548: public function getParamTags(): array
549: {
550: if ($this->paramTags === false) {
551: $this->paramTags = $this->phpDocNodeResolver->resolveParamTags(
552: $this->phpDocNode,
553: $this->getNameScope(),
554: );
555: }
556: return $this->paramTags;
557: }
558:
559: /**
560: * @return array<string, ParamOutTag>
561: */
562: public function getParamOutTags(): array
563: {
564: if ($this->paramOutTags === false) {
565: $this->paramOutTags = $this->phpDocNodeResolver->resolveParamOutTags(
566: $this->phpDocNode,
567: $this->getNameScope(),
568: );
569: }
570: return $this->paramOutTags;
571: }
572:
573: /**
574: * @return array<string, bool>
575: */
576: public function getParamsImmediatelyInvokedCallable(): array
577: {
578: if ($this->paramsImmediatelyInvokedCallable === false) {
579: $this->paramsImmediatelyInvokedCallable = $this->phpDocNodeResolver->resolveParamImmediatelyInvokedCallable($this->phpDocNode);
580: }
581:
582: return $this->paramsImmediatelyInvokedCallable;
583: }
584:
585: /**
586: * @return array<string, ParamClosureThisTag>
587: */
588: public function getParamClosureThisTags(): array
589: {
590: if ($this->paramClosureThisTags === false) {
591: $this->paramClosureThisTags = $this->phpDocNodeResolver->resolveParamClosureThisTags(
592: $this->phpDocNode,
593: $this->getNameScope(),
594: );
595: }
596:
597: return $this->paramClosureThisTags;
598: }
599:
600: public function getReturnTag(): ?ReturnTag
601: {
602: if (is_bool($this->returnTag)) {
603: $this->returnTag = $this->phpDocNodeResolver->resolveReturnTag(
604: $this->phpDocNode,
605: $this->getNameScope(),
606: );
607: }
608: return $this->returnTag;
609: }
610:
611: public function getThrowsTag(): ?ThrowsTag
612: {
613: if (is_bool($this->throwsTag)) {
614: $this->throwsTag = $this->phpDocNodeResolver->resolveThrowsTags(
615: $this->phpDocNode,
616: $this->getNameScope(),
617: );
618: }
619: return $this->throwsTag;
620: }
621:
622: /**
623: * @return array<MixinTag>
624: */
625: public function getMixinTags(): array
626: {
627: if ($this->mixinTags === false) {
628: $this->mixinTags = $this->phpDocNodeResolver->resolveMixinTags(
629: $this->phpDocNode,
630: $this->getNameScope(),
631: );
632: }
633:
634: return $this->mixinTags;
635: }
636:
637: /**
638: * @return array<RequireExtendsTag>
639: */
640: public function getRequireExtendsTags(): array
641: {
642: if ($this->requireExtendsTags === false) {
643: $this->requireExtendsTags = $this->phpDocNodeResolver->resolveRequireExtendsTags(
644: $this->phpDocNode,
645: $this->getNameScope(),
646: );
647: }
648:
649: return $this->requireExtendsTags;
650: }
651:
652: /**
653: * @return array<RequireImplementsTag>
654: */
655: public function getRequireImplementsTags(): array
656: {
657: if ($this->requireImplementsTags === false) {
658: $this->requireImplementsTags = $this->phpDocNodeResolver->resolveRequireImplementsTags(
659: $this->phpDocNode,
660: $this->getNameScope(),
661: );
662: }
663:
664: return $this->requireImplementsTags;
665: }
666:
667: /**
668: * @return array<TypeAliasTag>
669: */
670: public function getTypeAliasTags(): array
671: {
672: if ($this->typeAliasTags === false) {
673: $this->typeAliasTags = $this->phpDocNodeResolver->resolveTypeAliasTags(
674: $this->phpDocNode,
675: $this->getNameScope(),
676: );
677: }
678:
679: return $this->typeAliasTags;
680: }
681:
682: /**
683: * @return array<TypeAliasImportTag>
684: */
685: public function getTypeAliasImportTags(): array
686: {
687: if ($this->typeAliasImportTags === false) {
688: $this->typeAliasImportTags = $this->phpDocNodeResolver->resolveTypeAliasImportTags(
689: $this->phpDocNode,
690: $this->getNameScope(),
691: );
692: }
693:
694: return $this->typeAliasImportTags;
695: }
696:
697: /**
698: * @return array<AssertTag>
699: */
700: public function getAssertTags(): array
701: {
702: if ($this->assertTags === false) {
703: $this->assertTags = $this->phpDocNodeResolver->resolveAssertTags(
704: $this->phpDocNode,
705: $this->getNameScope(),
706: );
707: }
708:
709: return $this->assertTags;
710: }
711:
712: public function getSelfOutTag(): ?SelfOutTypeTag
713: {
714: if ($this->selfOutTypeTag === false) {
715: $this->selfOutTypeTag = $this->phpDocNodeResolver->resolveSelfOutTypeTag(
716: $this->phpDocNode,
717: $this->getNameScope(),
718: );
719: }
720:
721: return $this->selfOutTypeTag;
722: }
723:
724: public function getDeprecatedTag(): ?DeprecatedTag
725: {
726: if (is_bool($this->deprecatedTag)) {
727: $this->deprecatedTag = $this->phpDocNodeResolver->resolveDeprecatedTag(
728: $this->phpDocNode,
729: $this->getNameScope(),
730: );
731: }
732: return $this->deprecatedTag;
733: }
734:
735: public function isDeprecated(): bool
736: {
737: if ($this->isDeprecated === null) {
738: $this->isDeprecated = $this->phpDocNodeResolver->resolveIsDeprecated(
739: $this->phpDocNode,
740: );
741: }
742: return $this->isDeprecated;
743: }
744:
745: /**
746: * @internal
747: */
748: public function isNotDeprecated(): bool
749: {
750: if ($this->isNotDeprecated === null) {
751: $this->isNotDeprecated = $this->phpDocNodeResolver->resolveIsNotDeprecated(
752: $this->phpDocNode,
753: );
754: }
755: return $this->isNotDeprecated;
756: }
757:
758: public function isInternal(): bool
759: {
760: if ($this->isInternal === null) {
761: $this->isInternal = $this->phpDocNodeResolver->resolveIsInternal(
762: $this->phpDocNode,
763: );
764: }
765: return $this->isInternal;
766: }
767:
768: public function isFinal(): bool
769: {
770: if ($this->isFinal === null) {
771: $this->isFinal = $this->phpDocNodeResolver->resolveIsFinal(
772: $this->phpDocNode,
773: );
774: }
775: return $this->isFinal;
776: }
777:
778: public function hasConsistentConstructor(): bool
779: {
780: if ($this->hasConsistentConstructor === null) {
781: $this->hasConsistentConstructor = $this->phpDocNodeResolver->resolveHasConsistentConstructor(
782: $this->phpDocNode,
783: );
784: }
785: return $this->hasConsistentConstructor;
786: }
787:
788: public function acceptsNamedArguments(): bool
789: {
790: if ($this->acceptsNamedArguments === null) {
791: $this->acceptsNamedArguments = $this->phpDocNodeResolver->resolveAcceptsNamedArguments(
792: $this->phpDocNode,
793: );
794: }
795: return $this->acceptsNamedArguments;
796: }
797:
798: public function getTemplateTypeMap(): TemplateTypeMap
799: {
800: return $this->templateTypeMap;
801: }
802:
803: public function isPure(): ?bool
804: {
805: if ($this->isPure === 'notLoaded') {
806: $pure = $this->phpDocNodeResolver->resolveIsPure(
807: $this->phpDocNode,
808: );
809: if ($pure) {
810: $this->isPure = true;
811: return $this->isPure;
812: }
813:
814: $impure = $this->phpDocNodeResolver->resolveIsImpure(
815: $this->phpDocNode,
816: );
817: if ($impure) {
818: $this->isPure = false;
819: return $this->isPure;
820: }
821:
822: $this->isPure = null;
823: }
824:
825: return $this->isPure;
826: }
827:
828: public function isReadOnly(): bool
829: {
830: if ($this->isReadOnly === null) {
831: $this->isReadOnly = $this->phpDocNodeResolver->resolveIsReadOnly(
832: $this->phpDocNode,
833: );
834: }
835: return $this->isReadOnly;
836: }
837:
838: public function isImmutable(): bool
839: {
840: if ($this->isImmutable === null) {
841: $this->isImmutable = $this->phpDocNodeResolver->resolveIsImmutable(
842: $this->phpDocNode,
843: );
844: }
845: return $this->isImmutable;
846: }
847:
848: public function isAllowedPrivateMutation(): bool
849: {
850: if ($this->isAllowedPrivateMutation === null) {
851: $this->isAllowedPrivateMutation = $this->phpDocNodeResolver->resolveAllowPrivateMutation(
852: $this->phpDocNode,
853: );
854: }
855:
856: return $this->isAllowedPrivateMutation;
857: }
858:
859: /**
860: * @param array<string|int, VarTag> $varTags
861: * @param array<int, self> $parents
862: * @param array<int, PhpDocBlock> $parentPhpDocBlocks
863: * @return array<string|int, VarTag>
864: */
865: private static function mergeVarTags(array $varTags, array $parents, array $parentPhpDocBlocks): array
866: {
867: // Only allow one var tag per comment. Check the parent if child does not have this tag.
868: if (count($varTags) > 0) {
869: return $varTags;
870: }
871:
872: foreach ($parents as $i => $parent) {
873: $result = self::mergeOneParentVarTags($parent, $parentPhpDocBlocks[$i]);
874: if ($result === null) {
875: continue;
876: }
877:
878: return $result;
879: }
880:
881: return [];
882: }
883:
884: /**
885: * @return array<string|int, VarTag>|null
886: */
887: private static function mergeOneParentVarTags(self $parent, PhpDocBlock $phpDocBlock): ?array
888: {
889: foreach ($parent->getVarTags() as $key => $parentVarTag) {
890: return [$key => self::resolveTemplateTypeInTag($parentVarTag, $phpDocBlock, TemplateTypeVariance::createInvariant())];
891: }
892:
893: return null;
894: }
895:
896: /**
897: * @param array<string, ParamTag> $paramTags
898: * @param array<int, self> $parents
899: * @param array<int, PhpDocBlock> $parentPhpDocBlocks
900: * @return array<string, ParamTag>
901: */
902: private static function mergeParamTags(array $paramTags, array $parents, array $parentPhpDocBlocks): array
903: {
904: foreach ($parents as $i => $parent) {
905: $paramTags = self::mergeOneParentParamTags($paramTags, $parent, $parentPhpDocBlocks[$i]);
906: }
907:
908: return $paramTags;
909: }
910:
911: /**
912: * @param array<string, ParamTag> $paramTags
913: * @return array<string, ParamTag>
914: */
915: private static function mergeOneParentParamTags(array $paramTags, self $parent, PhpDocBlock $phpDocBlock): array
916: {
917: $parentParamTags = $phpDocBlock->transformArrayKeysWithParameterNameMapping($parent->getParamTags());
918:
919: foreach ($parentParamTags as $name => $parentParamTag) {
920: if (array_key_exists($name, $paramTags)) {
921: continue;
922: }
923:
924: $paramTags[$name] = self::resolveTemplateTypeInTag(
925: $parentParamTag->withType($phpDocBlock->transformConditionalReturnTypeWithParameterNameMapping($parentParamTag->getType())),
926: $phpDocBlock,
927: TemplateTypeVariance::createContravariant(),
928: );
929: }
930:
931: return $paramTags;
932: }
933:
934: /**
935: * @param array<int, self> $parents
936: * @param array<int, PhpDocBlock> $parentPhpDocBlocks
937: * @return ReturnTag|Null
938: */
939: private static function mergeReturnTags(?ReturnTag $returnTag, ?ClassReflection $classReflection, array $parents, array $parentPhpDocBlocks): ?ReturnTag
940: {
941: if ($returnTag !== null) {
942: return $returnTag;
943: }
944:
945: foreach ($parents as $i => $parent) {
946: $result = self::mergeOneParentReturnTag($returnTag, $classReflection, $parent, $parentPhpDocBlocks[$i]);
947: if ($result === null) {
948: continue;
949: }
950:
951: return $result;
952: }
953:
954: return null;
955: }
956:
957: private static function mergeOneParentReturnTag(?ReturnTag $returnTag, ?ClassReflection $classReflection, self $parent, PhpDocBlock $phpDocBlock): ?ReturnTag
958: {
959: $parentReturnTag = $parent->getReturnTag();
960: if ($parentReturnTag === null) {
961: return $returnTag;
962: }
963:
964: $parentType = $parentReturnTag->getType();
965:
966: if ($classReflection !== null) {
967: $parentType = TypeTraverser::map(
968: $parentType,
969: static function (Type $type, callable $traverse) use ($classReflection): Type {
970: if ($type instanceof StaticType) {
971: return $type->changeBaseClass($classReflection);
972: }
973:
974: return $traverse($type);
975: },
976: );
977:
978: $parentReturnTag = $parentReturnTag->withType($parentType);
979: }
980:
981: // Each parent would overwrite the previous one except if it returns a less specific type.
982: // Do not care for incompatible types as there is a separate rule for that.
983: if ($returnTag !== null && $parentType->isSuperTypeOf($returnTag->getType())->yes()) {
984: return null;
985: }
986:
987: return self::resolveTemplateTypeInTag(
988: $parentReturnTag->withType(
989: $phpDocBlock->transformConditionalReturnTypeWithParameterNameMapping($parentReturnTag->getType()),
990: )->toImplicit(),
991: $phpDocBlock,
992: TemplateTypeVariance::createCovariant(),
993: );
994: }
995:
996: /**
997: * @param array<AssertTag> $assertTags
998: * @param array<int, self> $parents
999: * @param array<int, PhpDocBlock> $parentPhpDocBlocks
1000: * @return array<AssertTag>
1001: */
1002: private static function mergeAssertTags(array $assertTags, array $parents, array $parentPhpDocBlocks): array
1003: {
1004: if (count($assertTags) > 0) {
1005: return $assertTags;
1006: }
1007: foreach ($parents as $i => $parent) {
1008: $result = $parent->getAssertTags();
1009: if (count($result) === 0) {
1010: continue;
1011: }
1012:
1013: $phpDocBlock = $parentPhpDocBlocks[$i];
1014:
1015: return array_map(
1016: static fn (AssertTag $assertTag) => self::resolveTemplateTypeInTag(
1017: $assertTag->withParameter(
1018: $phpDocBlock->transformAssertTagParameterWithParameterNameMapping($assertTag->getParameter()),
1019: )->toImplicit(),
1020: $phpDocBlock,
1021: TemplateTypeVariance::createCovariant(),
1022: ),
1023: $result,
1024: );
1025: }
1026:
1027: return $assertTags;
1028: }
1029:
1030: /**
1031: * @param array<int, self> $parents
1032: */
1033: private static function mergeSelfOutTypeTags(?SelfOutTypeTag $selfOutTypeTag, array $parents): ?SelfOutTypeTag
1034: {
1035: if ($selfOutTypeTag !== null) {
1036: return $selfOutTypeTag;
1037: }
1038: foreach ($parents as $parent) {
1039: $result = $parent->getSelfOutTag();
1040: if ($result === null) {
1041: continue;
1042: }
1043: return $result;
1044: }
1045:
1046: return null;
1047: }
1048:
1049: /**
1050: * @param array<int, self> $parents
1051: */
1052: private static function mergeDeprecatedTags(?DeprecatedTag $deprecatedTag, bool $hasNotDeprecatedTag, array $parents): ?DeprecatedTag
1053: {
1054: if ($deprecatedTag !== null) {
1055: return $deprecatedTag;
1056: }
1057:
1058: if ($hasNotDeprecatedTag) {
1059: return null;
1060: }
1061:
1062: foreach ($parents as $parent) {
1063: $result = $parent->getDeprecatedTag();
1064: if ($result === null && !$parent->isNotDeprecated()) {
1065: continue;
1066: }
1067: return $result;
1068: }
1069:
1070: return null;
1071: }
1072:
1073: /**
1074: * @param array<int, self> $parents
1075: */
1076: private static function mergeThrowsTags(?ThrowsTag $throwsTag, array $parents): ?ThrowsTag
1077: {
1078: if ($throwsTag !== null) {
1079: return $throwsTag;
1080: }
1081: foreach ($parents as $parent) {
1082: $result = $parent->getThrowsTag();
1083: if ($result === null) {
1084: continue;
1085: }
1086:
1087: return $result;
1088: }
1089:
1090: return null;
1091: }
1092:
1093: /**
1094: * @param array<string, ParamOutTag> $paramOutTags
1095: * @param array<int, self> $parents
1096: * @param array<int, PhpDocBlock> $parentPhpDocBlocks
1097: * @return array<string, ParamOutTag>
1098: */
1099: private static function mergeParamOutTags(array $paramOutTags, array $parents, array $parentPhpDocBlocks): array
1100: {
1101: foreach ($parents as $i => $parent) {
1102: $paramOutTags = self::mergeOneParentParamOutTags($paramOutTags, $parent, $parentPhpDocBlocks[$i]);
1103: }
1104:
1105: return $paramOutTags;
1106: }
1107:
1108: /**
1109: * @param array<string, ParamOutTag> $paramOutTags
1110: * @return array<string, ParamOutTag>
1111: */
1112: private static function mergeOneParentParamOutTags(array $paramOutTags, self $parent, PhpDocBlock $phpDocBlock): array
1113: {
1114: $parentParamOutTags = $phpDocBlock->transformArrayKeysWithParameterNameMapping($parent->getParamOutTags());
1115:
1116: foreach ($parentParamOutTags as $name => $parentParamTag) {
1117: if (array_key_exists($name, $paramOutTags)) {
1118: continue;
1119: }
1120:
1121: $paramOutTags[$name] = self::resolveTemplateTypeInTag(
1122: $parentParamTag->withType($phpDocBlock->transformConditionalReturnTypeWithParameterNameMapping($parentParamTag->getType())),
1123: $phpDocBlock,
1124: TemplateTypeVariance::createCovariant(),
1125: );
1126: }
1127:
1128: return $paramOutTags;
1129: }
1130:
1131: /**
1132: * @param array<string, bool> $paramsImmediatelyInvokedCallable
1133: * @param array<int, self> $parents
1134: * @param array<int, PhpDocBlock> $parentPhpDocBlocks
1135: * @return array<string, bool>
1136: */
1137: private static function mergeParamsImmediatelyInvokedCallable(array $paramsImmediatelyInvokedCallable, array $parents, array $parentPhpDocBlocks): array
1138: {
1139: foreach ($parents as $i => $parent) {
1140: $paramsImmediatelyInvokedCallable = self::mergeOneParentParamImmediatelyInvokedCallable($paramsImmediatelyInvokedCallable, $parent, $parentPhpDocBlocks[$i]);
1141: }
1142:
1143: return $paramsImmediatelyInvokedCallable;
1144: }
1145:
1146: /**
1147: * @param array<string, bool> $paramsImmediatelyInvokedCallable
1148: * @return array<string, bool>
1149: */
1150: private static function mergeOneParentParamImmediatelyInvokedCallable(array $paramsImmediatelyInvokedCallable, self $parent, PhpDocBlock $phpDocBlock): array
1151: {
1152: $parentImmediatelyInvokedCallable = $phpDocBlock->transformArrayKeysWithParameterNameMapping($parent->getParamsImmediatelyInvokedCallable());
1153:
1154: foreach ($parentImmediatelyInvokedCallable as $name => $parentIsImmediatelyInvokedCallable) {
1155: if (array_key_exists($name, $paramsImmediatelyInvokedCallable)) {
1156: continue;
1157: }
1158:
1159: $paramsImmediatelyInvokedCallable[$name] = $parentIsImmediatelyInvokedCallable;
1160: }
1161:
1162: return $paramsImmediatelyInvokedCallable;
1163: }
1164:
1165: /**
1166: * @param array<string, ParamClosureThisTag> $paramsClosureThisTags
1167: * @param array<int, self> $parents
1168: * @param array<int, PhpDocBlock> $parentPhpDocBlocks
1169: * @return array<string, ParamClosureThisTag>
1170: */
1171: private static function mergeParamClosureThisTags(array $paramsClosureThisTags, array $parents, array $parentPhpDocBlocks): array
1172: {
1173: foreach ($parents as $i => $parent) {
1174: $paramsClosureThisTags = self::mergeOneParentParamClosureThisTag($paramsClosureThisTags, $parent, $parentPhpDocBlocks[$i]);
1175: }
1176:
1177: return $paramsClosureThisTags;
1178: }
1179:
1180: /**
1181: * @param array<string, ParamClosureThisTag> $paramsClosureThisTags
1182: * @return array<string, ParamClosureThisTag>
1183: */
1184: private static function mergeOneParentParamClosureThisTag(array $paramsClosureThisTags, self $parent, PhpDocBlock $phpDocBlock): array
1185: {
1186: $parentClosureThisTags = $phpDocBlock->transformArrayKeysWithParameterNameMapping($parent->getParamClosureThisTags());
1187:
1188: foreach ($parentClosureThisTags as $name => $parentParamClosureThisTag) {
1189: if (array_key_exists($name, $paramsClosureThisTags)) {
1190: continue;
1191: }
1192:
1193: $paramsClosureThisTags[$name] = self::resolveTemplateTypeInTag(
1194: $parentParamClosureThisTag->withType(
1195: $phpDocBlock->transformConditionalReturnTypeWithParameterNameMapping($parentParamClosureThisTag->getType()),
1196: ),
1197: $phpDocBlock,
1198: TemplateTypeVariance::createContravariant(),
1199: );
1200: }
1201:
1202: return $paramsClosureThisTags;
1203: }
1204:
1205: /**
1206: * @param array<int, self> $parents
1207: */
1208: private static function mergePureTags(?bool $isPure, array $parents): ?bool
1209: {
1210: if ($isPure !== null) {
1211: return $isPure;
1212: }
1213:
1214: foreach ($parents as $parent) {
1215: $parentIsPure = $parent->isPure();
1216: if ($parentIsPure === null) {
1217: continue;
1218: }
1219:
1220: return $parentIsPure;
1221: }
1222:
1223: return null;
1224: }
1225:
1226: /**
1227: * @template T of TypedTag
1228: * @param T $tag
1229: * @return T
1230: */
1231: private static function resolveTemplateTypeInTag(
1232: TypedTag $tag,
1233: PhpDocBlock $phpDocBlock,
1234: TemplateTypeVariance $positionVariance,
1235: ): TypedTag
1236: {
1237: $type = TemplateTypeHelper::resolveTemplateTypes(
1238: $tag->getType(),
1239: $phpDocBlock->getClassReflection()->getActiveTemplateTypeMap(),
1240: $phpDocBlock->getClassReflection()->getCallSiteVarianceMap(),
1241: $positionVariance,
1242: );
1243: return $tag->withType($type);
1244: }
1245:
1246: }
1247: