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