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: | |
45: | |
46: | |
47: | class ResolvedPhpDocBlock |
48: | { |
49: | |
50: | public const EMPTY_DOC_STRING = '/** */'; |
51: | |
52: | private PhpDocNode $phpDocNode; |
53: | |
54: | |
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: | |
66: | private array $templateTags; |
67: | |
68: | private PhpDocNodeResolver $phpDocNodeResolver; |
69: | |
70: | private ReflectionProvider $reflectionProvider; |
71: | |
72: | |
73: | private array|false $varTags = false; |
74: | |
75: | |
76: | private array|false $methodTags = false; |
77: | |
78: | |
79: | private array|false $propertyTags = false; |
80: | |
81: | |
82: | private array|false $extendsTags = false; |
83: | |
84: | |
85: | private array|false $implementsTags = false; |
86: | |
87: | |
88: | private array|false $usesTags = false; |
89: | |
90: | |
91: | private array|false $paramTags = false; |
92: | |
93: | |
94: | private array|false $paramOutTags = false; |
95: | |
96: | |
97: | private array|false $paramsImmediatelyInvokedCallable = false; |
98: | |
99: | |
100: | private array|false $paramClosureThisTags = false; |
101: | |
102: | private ReturnTag|false|null $returnTag = false; |
103: | |
104: | private ThrowsTag|false|null $throwsTag = false; |
105: | |
106: | |
107: | private array|false $mixinTags = false; |
108: | |
109: | |
110: | private array|false $requireExtendsTags = false; |
111: | |
112: | |
113: | private array|false $requireImplementsTags = false; |
114: | |
115: | |
116: | private array|false $typeAliasTags = false; |
117: | |
118: | |
119: | private array|false $typeAliasImportTags = false; |
120: | |
121: | |
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: | |
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: | |
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: | |
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: | |
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: | |
243: | |
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: | |
253: | $result = new self(); |
254: | |
255: | |
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: | |
268: | $result->templateTypeMap = $this->templateTypeMap; |
269: | $result->templateTags = $this->templateTags; |
270: | |
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: | |
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: | |
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: | |
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: | |
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: | |
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: | |
497: | |
498: | public function getTemplateTags(): array |
499: | { |
500: | return $this->templateTags; |
501: | } |
502: | |
503: | |
504: | |
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: | |
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: | |
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: | |
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: | |
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: | |
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: | |
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: | |
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: | |
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: | |
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: | |
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: | |
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: | |
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: | |
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: | |
861: | |
862: | |
863: | |
864: | |
865: | private static function mergeVarTags(array $varTags, array $parents, array $parentPhpDocBlocks): array |
866: | { |
867: | |
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: | |
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: | |
898: | |
899: | |
900: | |
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: | |
913: | |
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: | |
936: | |
937: | |
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: | |
982: | |
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: | |
998: | |
999: | |
1000: | |
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: | |
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: | |
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: | |
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: | |
1095: | |
1096: | |
1097: | |
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: | |
1110: | |
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: | |
1133: | |
1134: | |
1135: | |
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: | |
1148: | |
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: | |
1167: | |
1168: | |
1169: | |
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: | |
1182: | |
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: | |
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: | |
1228: | |
1229: | |
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: | |