1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Reflection;
4:
5: use Attribute;
6: use PhpParser\Node\Arg;
7: use PhpParser\Node\Expr\StaticCall;
8: use PhpParser\Node\Identifier;
9: use PhpParser\Node\Name\FullyQualified;
10: use PHPStan\Analyser\ArgumentsNormalizer;
11: use PHPStan\BetterReflection\Reflection\Adapter\ReflectionClass;
12: use PHPStan\BetterReflection\Reflection\Adapter\ReflectionEnum;
13: use PHPStan\BetterReflection\Reflection\Adapter\ReflectionEnumBackedCase;
14: use PHPStan\BetterReflection\Reflection\Adapter\ReflectionMethod;
15: use PHPStan\Php\PhpVersion;
16: use PHPStan\PhpDoc\PhpDocInheritanceResolver;
17: use PHPStan\PhpDoc\ResolvedPhpDocBlock;
18: use PHPStan\PhpDoc\StubPhpDocProvider;
19: use PHPStan\PhpDoc\Tag\ExtendsTag;
20: use PHPStan\PhpDoc\Tag\ImplementsTag;
21: use PHPStan\PhpDoc\Tag\MethodTag;
22: use PHPStan\PhpDoc\Tag\MixinTag;
23: use PHPStan\PhpDoc\Tag\PropertyTag;
24: use PHPStan\PhpDoc\Tag\RequireExtendsTag;
25: use PHPStan\PhpDoc\Tag\RequireImplementsTag;
26: use PHPStan\PhpDoc\Tag\TemplateTag;
27: use PHPStan\PhpDoc\Tag\TypeAliasImportTag;
28: use PHPStan\PhpDoc\Tag\TypeAliasTag;
29: use PHPStan\Reflection\Php\PhpClassReflectionExtension;
30: use PHPStan\Reflection\Php\PhpPropertyReflection;
31: use PHPStan\Reflection\Php\UniversalObjectCratesClassReflectionExtension;
32: use PHPStan\Reflection\RequireExtension\RequireExtendsMethodsClassReflectionExtension;
33: use PHPStan\Reflection\RequireExtension\RequireExtendsPropertiesClassReflectionExtension;
34: use PHPStan\Reflection\SignatureMap\SignatureMapProvider;
35: use PHPStan\ShouldNotHappenException;
36: use PHPStan\Type\CircularTypeAliasDefinitionException;
37: use PHPStan\Type\Constant\ConstantIntegerType;
38: use PHPStan\Type\ErrorType;
39: use PHPStan\Type\FileTypeMapper;
40: use PHPStan\Type\Generic\GenericObjectType;
41: use PHPStan\Type\Generic\TemplateTypeFactory;
42: use PHPStan\Type\Generic\TemplateTypeHelper;
43: use PHPStan\Type\Generic\TemplateTypeMap;
44: use PHPStan\Type\Generic\TemplateTypeScope;
45: use PHPStan\Type\Generic\TemplateTypeVariance;
46: use PHPStan\Type\Generic\TemplateTypeVarianceMap;
47: use PHPStan\Type\Generic\TypeProjectionHelper;
48: use PHPStan\Type\ObjectType;
49: use PHPStan\Type\Type;
50: use PHPStan\Type\TypeAlias;
51: use PHPStan\Type\TypehintHelper;
52: use PHPStan\Type\VerbosityLevel;
53: use ReflectionException;
54: use function array_diff;
55: use function array_filter;
56: use function array_key_exists;
57: use function array_map;
58: use function array_merge;
59: use function array_shift;
60: use function array_unique;
61: use function array_values;
62: use function count;
63: use function implode;
64: use function in_array;
65: use function is_bool;
66: use function is_file;
67: use function is_int;
68: use function reset;
69: use function sprintf;
70: use function strtolower;
71:
72: /**
73: * @api
74: * @final
75: */
76: class ClassReflection
77: {
78:
79: /** @var ExtendedMethodReflection[] */
80: private array $methods = [];
81:
82: /** @var ExtendedPropertyReflection[] */
83: private array $properties = [];
84:
85: /** @var ClassConstantReflection[] */
86: private array $constants = [];
87:
88: /** @var EnumCaseReflection[]|null */
89: private ?array $enumCases = null;
90:
91: /** @var int[]|null */
92: private ?array $classHierarchyDistances = null;
93:
94: private ?string $deprecatedDescription = null;
95:
96: private ?bool $isDeprecated = null;
97:
98: private ?bool $isGeneric = null;
99:
100: private ?bool $isInternal = null;
101:
102: private ?bool $isFinal = null;
103:
104: private ?bool $isImmutable = null;
105:
106: private ?bool $hasConsistentConstructor = null;
107:
108: private ?bool $acceptsNamedArguments = null;
109:
110: private ?TemplateTypeMap $templateTypeMap = null;
111:
112: private ?TemplateTypeMap $activeTemplateTypeMap = null;
113:
114: private ?TemplateTypeVarianceMap $defaultCallSiteVarianceMap = null;
115:
116: private ?TemplateTypeVarianceMap $callSiteVarianceMap = null;
117:
118: /** @var array<string,ClassReflection>|null */
119: private ?array $ancestors = null;
120:
121: private ?string $cacheKey = null;
122:
123: /** @var array<string, bool> */
124: private array $subclasses = [];
125:
126: private string|false|null $filename = false;
127:
128: private string|false|null $reflectionDocComment = false;
129:
130: private false|ResolvedPhpDocBlock $resolvedPhpDocBlock = false;
131:
132: private false|ResolvedPhpDocBlock $traitContextResolvedPhpDocBlock = false;
133:
134: /** @var ClassReflection[]|null */
135: private ?array $cachedInterfaces = null;
136:
137: private ClassReflection|false|null $cachedParentClass = false;
138:
139: /** @var array<string, TypeAlias>|null */
140: private ?array $typeAliases = null;
141:
142: /** @var array<string, true> */
143: private static array $resolvingTypeAliasImports = [];
144:
145: /**
146: * @param PropertiesClassReflectionExtension[] $propertiesClassReflectionExtensions
147: * @param MethodsClassReflectionExtension[] $methodsClassReflectionExtensions
148: * @param AllowedSubTypesClassReflectionExtension[] $allowedSubTypesClassReflectionExtensions
149: * @param string[] $universalObjectCratesClasses
150: */
151: public function __construct(
152: private ReflectionProvider $reflectionProvider,
153: private InitializerExprTypeResolver $initializerExprTypeResolver,
154: private FileTypeMapper $fileTypeMapper,
155: private StubPhpDocProvider $stubPhpDocProvider,
156: private PhpDocInheritanceResolver $phpDocInheritanceResolver,
157: private PhpVersion $phpVersion,
158: private SignatureMapProvider $signatureMapProvider,
159: private array $propertiesClassReflectionExtensions,
160: private array $methodsClassReflectionExtensions,
161: private array $allowedSubTypesClassReflectionExtensions,
162: private RequireExtendsPropertiesClassReflectionExtension $requireExtendsPropertiesClassReflectionExtension,
163: private RequireExtendsMethodsClassReflectionExtension $requireExtendsMethodsClassReflectionExtension,
164: private string $displayName,
165: private ReflectionClass|ReflectionEnum $reflection,
166: private ?string $anonymousFilename,
167: private ?TemplateTypeMap $resolvedTemplateTypeMap,
168: private ?ResolvedPhpDocBlock $stubPhpDocBlock,
169: private array $universalObjectCratesClasses,
170: private ?string $extraCacheKey = null,
171: private ?TemplateTypeVarianceMap $resolvedCallSiteVarianceMap = null,
172: )
173: {
174: }
175:
176: public function getNativeReflection(): ReflectionClass|ReflectionEnum
177: {
178: return $this->reflection;
179: }
180:
181: public function getFileName(): ?string
182: {
183: if (!is_bool($this->filename)) {
184: return $this->filename;
185: }
186:
187: if ($this->anonymousFilename !== null) {
188: return $this->filename = $this->anonymousFilename;
189: }
190: $fileName = $this->reflection->getFileName();
191: if ($fileName === false) {
192: return $this->filename = null;
193: }
194:
195: if (!is_file($fileName)) {
196: return $this->filename = null;
197: }
198:
199: return $this->filename = $fileName;
200: }
201:
202: /**
203: * @deprecated Use getFileName()
204: */
205: public function getFileNameWithPhpDocs(): ?string
206: {
207: return $this->getFileName();
208: }
209:
210: public function getParentClass(): ?ClassReflection
211: {
212: if (!is_bool($this->cachedParentClass)) {
213: return $this->cachedParentClass;
214: }
215:
216: $parentClass = $this->reflection->getParentClass();
217:
218: if ($parentClass === false) {
219: return $this->cachedParentClass = null;
220: }
221:
222: $extendsTag = $this->getFirstExtendsTag();
223:
224: if ($extendsTag !== null && $this->isValidAncestorType($extendsTag->getType(), [$parentClass->getName()])) {
225: $extendedType = $extendsTag->getType();
226:
227: if ($this->isGeneric()) {
228: $extendedType = TemplateTypeHelper::resolveTemplateTypes(
229: $extendedType,
230: $this->getPossiblyIncompleteActiveTemplateTypeMap(),
231: $this->getCallSiteVarianceMap(),
232: TemplateTypeVariance::createStatic(),
233: );
234: }
235:
236: if (!$extendedType instanceof GenericObjectType) {
237: return $this->reflectionProvider->getClass($parentClass->getName());
238: }
239:
240: return $extendedType->getClassReflection() ?? $this->reflectionProvider->getClass($parentClass->getName());
241: }
242:
243: $parentReflection = $this->reflectionProvider->getClass($parentClass->getName());
244: if ($parentReflection->isGeneric()) {
245: return $parentReflection->withTypes(
246: array_values($parentReflection->getTemplateTypeMap()->map(static fn (): Type => new ErrorType())->getTypes()),
247: );
248: }
249:
250: $this->cachedParentClass = $parentReflection;
251:
252: return $parentReflection;
253: }
254:
255: /**
256: * @return class-string
257: */
258: public function getName(): string
259: {
260: return $this->reflection->getName();
261: }
262:
263: public function getDisplayName(bool $withTemplateTypes = true): string
264: {
265: if (
266: $withTemplateTypes === false
267: || $this->resolvedTemplateTypeMap === null
268: || count($this->resolvedTemplateTypeMap->getTypes()) === 0
269: ) {
270: return $this->displayName;
271: }
272:
273: $templateTypes = [];
274: $variances = $this->getCallSiteVarianceMap()->getVariances();
275: foreach ($this->getActiveTemplateTypeMap()->getTypes() as $name => $templateType) {
276: $variance = $variances[$name] ?? null;
277: if ($variance === null) {
278: continue;
279: }
280:
281: $templateTypes[] = TypeProjectionHelper::describe($templateType, $variance, VerbosityLevel::typeOnly());
282: }
283:
284: return $this->displayName . '<' . implode(',', $templateTypes) . '>';
285: }
286:
287: public function getCacheKey(): string
288: {
289: $cacheKey = $this->cacheKey;
290: if ($cacheKey !== null) {
291: return $this->cacheKey;
292: }
293:
294: $cacheKey = $this->displayName;
295:
296: if ($this->resolvedTemplateTypeMap !== null) {
297: $templateTypes = [];
298: $variances = $this->getCallSiteVarianceMap()->getVariances();
299: foreach ($this->getActiveTemplateTypeMap()->getTypes() as $name => $templateType) {
300: $variance = $variances[$name] ?? null;
301: if ($variance === null) {
302: continue;
303: }
304:
305: $templateTypes[] = TypeProjectionHelper::describe($templateType, $variance, VerbosityLevel::cache());
306: }
307:
308: $cacheKey .= '<' . implode(',', $templateTypes) . '>';
309: }
310:
311: if ($this->extraCacheKey !== null) {
312: $cacheKey .= '-' . $this->extraCacheKey;
313: }
314:
315: $this->cacheKey = $cacheKey;
316:
317: return $cacheKey;
318: }
319:
320: /**
321: * @return int[]
322: */
323: public function getClassHierarchyDistances(): array
324: {
325: if ($this->classHierarchyDistances === null) {
326: $distance = 0;
327: $distances = [
328: $this->getName() => $distance,
329: ];
330: $currentClassReflection = $this->getNativeReflection();
331: foreach ($this->collectTraits($this->getNativeReflection()) as $trait) {
332: $distance++;
333: if (array_key_exists($trait->getName(), $distances)) {
334: continue;
335: }
336:
337: $distances[$trait->getName()] = $distance;
338: }
339:
340: while ($currentClassReflection->getParentClass() !== false) {
341: $distance++;
342: $parentClassName = $currentClassReflection->getParentClass()->getName();
343: if (!array_key_exists($parentClassName, $distances)) {
344: $distances[$parentClassName] = $distance;
345: }
346: $currentClassReflection = $currentClassReflection->getParentClass();
347: foreach ($this->collectTraits($currentClassReflection) as $trait) {
348: $distance++;
349: if (array_key_exists($trait->getName(), $distances)) {
350: continue;
351: }
352:
353: $distances[$trait->getName()] = $distance;
354: }
355: }
356: foreach ($this->getNativeReflection()->getInterfaces() as $interface) {
357: $distance++;
358: if (array_key_exists($interface->getName(), $distances)) {
359: continue;
360: }
361:
362: $distances[$interface->getName()] = $distance;
363: }
364:
365: $this->classHierarchyDistances = $distances;
366: }
367:
368: return $this->classHierarchyDistances;
369: }
370:
371: /**
372: * @return ReflectionClass[]
373: */
374: private function collectTraits(ReflectionClass|ReflectionEnum $class): array
375: {
376: $traits = [];
377: $traitsLeftToAnalyze = $class->getTraits();
378:
379: while (count($traitsLeftToAnalyze) !== 0) {
380: $trait = reset($traitsLeftToAnalyze);
381: $traits[] = $trait;
382:
383: foreach ($trait->getTraits() as $subTrait) {
384: if (in_array($subTrait, $traits, true)) {
385: continue;
386: }
387:
388: $traitsLeftToAnalyze[] = $subTrait;
389: }
390:
391: array_shift($traitsLeftToAnalyze);
392: }
393:
394: return $traits;
395: }
396:
397: public function allowsDynamicProperties(): bool
398: {
399: if ($this->isEnum()) {
400: return false;
401: }
402:
403: if (!$this->phpVersion->deprecatesDynamicProperties()) {
404: return true;
405: }
406:
407: if ($this->isReadOnly()) {
408: return false;
409: }
410:
411: if (UniversalObjectCratesClassReflectionExtension::isUniversalObjectCrate(
412: $this->reflectionProvider,
413: $this->universalObjectCratesClasses,
414: $this,
415: )) {
416: return true;
417: }
418:
419: $class = $this;
420: $attributes = $class->reflection->getAttributes('AllowDynamicProperties');
421: while (count($attributes) === 0 && $class->getParentClass() !== null) {
422: $attributes = $class->getParentClass()->reflection->getAttributes('AllowDynamicProperties');
423: $class = $class->getParentClass();
424: }
425:
426: return count($attributes) > 0;
427: }
428:
429: private function allowsDynamicPropertiesExtensions(): bool
430: {
431: if ($this->allowsDynamicProperties()) {
432: return true;
433: }
434:
435: $hasMagicMethod = $this->hasNativeMethod('__get') || $this->hasNativeMethod('__set') || $this->hasNativeMethod('__isset');
436: if ($hasMagicMethod) {
437: return true;
438: }
439:
440: foreach ($this->getRequireExtendsTags() as $extendsTag) {
441: $type = $extendsTag->getType();
442: if (!$type instanceof ObjectType) {
443: continue;
444: }
445:
446: $reflection = $type->getClassReflection();
447: if ($reflection === null) {
448: continue;
449: }
450:
451: if (!$reflection->allowsDynamicPropertiesExtensions()) {
452: continue;
453: }
454:
455: return true;
456: }
457:
458: return false;
459: }
460:
461: public function hasProperty(string $propertyName): bool
462: {
463: if ($this->isEnum()) {
464: return $this->hasNativeProperty($propertyName);
465: }
466:
467: foreach ($this->propertiesClassReflectionExtensions as $i => $extension) {
468: if ($i > 0 && !$this->allowsDynamicPropertiesExtensions()) {
469: break;
470: }
471: if ($extension->hasProperty($this, $propertyName)) {
472: return true;
473: }
474: }
475:
476: if ($this->requireExtendsPropertiesClassReflectionExtension->hasProperty($this, $propertyName)) {
477: return true;
478: }
479:
480: return false;
481: }
482:
483: public function hasMethod(string $methodName): bool
484: {
485: foreach ($this->methodsClassReflectionExtensions as $extension) {
486: if ($extension->hasMethod($this, $methodName)) {
487: return true;
488: }
489: }
490:
491: if ($this->requireExtendsMethodsClassReflectionExtension->hasMethod($this, $methodName)) {
492: return true;
493: }
494:
495: return false;
496: }
497:
498: public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): ExtendedMethodReflection
499: {
500: $key = $methodName;
501: if ($scope->isInClass()) {
502: $key = sprintf('%s-%s', $key, $scope->getClassReflection()->getCacheKey());
503: }
504:
505: if (!isset($this->methods[$key])) {
506: foreach ($this->methodsClassReflectionExtensions as $extension) {
507: if (!$extension->hasMethod($this, $methodName)) {
508: continue;
509: }
510:
511: $method = $this->wrapExtendedMethod($extension->getMethod($this, $methodName));
512: if ($scope->canCallMethod($method)) {
513: return $this->methods[$key] = $method;
514: }
515: $this->methods[$key] = $method;
516: }
517: }
518:
519: if (!isset($this->methods[$key])) {
520: if ($this->requireExtendsMethodsClassReflectionExtension->hasMethod($this, $methodName)) {
521: $method = $this->requireExtendsMethodsClassReflectionExtension->getMethod($this, $methodName);
522: $this->methods[$key] = $method;
523: }
524: }
525:
526: if (!isset($this->methods[$key])) {
527: throw new MissingMethodFromReflectionException($this->getName(), $methodName);
528: }
529:
530: return $this->methods[$key];
531: }
532:
533: private function wrapExtendedMethod(MethodReflection $method): ExtendedMethodReflection
534: {
535: if ($method instanceof ExtendedMethodReflection) {
536: return $method;
537: }
538:
539: return new WrappedExtendedMethodReflection($method);
540: }
541:
542: private function wrapExtendedProperty(PropertyReflection $method): ExtendedPropertyReflection
543: {
544: if ($method instanceof ExtendedPropertyReflection) {
545: return $method;
546: }
547:
548: return new WrappedExtendedPropertyReflection($method);
549: }
550:
551: public function hasNativeMethod(string $methodName): bool
552: {
553: return $this->getPhpExtension()->hasNativeMethod($this, $methodName);
554: }
555:
556: public function getNativeMethod(string $methodName): ExtendedMethodReflection
557: {
558: if (!$this->hasNativeMethod($methodName)) {
559: throw new MissingMethodFromReflectionException($this->getName(), $methodName);
560: }
561: return $this->getPhpExtension()->getNativeMethod($this, $methodName);
562: }
563:
564: public function hasConstructor(): bool
565: {
566: return $this->findConstructor() !== null;
567: }
568:
569: public function getConstructor(): ExtendedMethodReflection
570: {
571: $constructor = $this->findConstructor();
572: if ($constructor === null) {
573: throw new ShouldNotHappenException();
574: }
575: return $this->getNativeMethod($constructor->getName());
576: }
577:
578: private function findConstructor(): ?ReflectionMethod
579: {
580: $constructor = $this->reflection->getConstructor();
581: if ($constructor === null) {
582: return null;
583: }
584:
585: if ($this->phpVersion->supportsLegacyConstructor()) {
586: return $constructor;
587: }
588:
589: if (strtolower($constructor->getName()) !== '__construct') {
590: return null;
591: }
592:
593: return $constructor;
594: }
595:
596: private function getPhpExtension(): PhpClassReflectionExtension
597: {
598: $extension = $this->methodsClassReflectionExtensions[0];
599: if (!$extension instanceof PhpClassReflectionExtension) {
600: throw new ShouldNotHappenException();
601: }
602:
603: return $extension;
604: }
605:
606: /** @internal */
607: public function evictPrivateSymbols(): void
608: {
609: foreach ($this->constants as $name => $constant) {
610: if (!$constant->isPrivate()) {
611: continue;
612: }
613:
614: unset($this->constants[$name]);
615: }
616: foreach ($this->properties as $name => $property) {
617: if (!$property->isPrivate()) {
618: continue;
619: }
620:
621: unset($this->properties[$name]);
622: }
623: foreach ($this->methods as $name => $method) {
624: if (!$method->isPrivate()) {
625: continue;
626: }
627:
628: unset($this->methods[$name]);
629: }
630: $this->getPhpExtension()->evictPrivateSymbols($this->getCacheKey());
631: }
632:
633: public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
634: {
635: if ($this->isEnum()) {
636: return $this->getNativeProperty($propertyName);
637: }
638:
639: $key = $propertyName;
640: if ($scope->isInClass()) {
641: $key = sprintf('%s-%s', $key, $scope->getClassReflection()->getCacheKey());
642: }
643:
644: if (!isset($this->properties[$key])) {
645: foreach ($this->propertiesClassReflectionExtensions as $i => $extension) {
646: if ($i > 0 && !$this->allowsDynamicPropertiesExtensions()) {
647: break;
648: }
649:
650: if (!$extension->hasProperty($this, $propertyName)) {
651: continue;
652: }
653:
654: $property = $this->wrapExtendedProperty($extension->getProperty($this, $propertyName));
655: if ($scope->canAccessProperty($property)) {
656: return $this->properties[$key] = $property;
657: }
658: $this->properties[$key] = $property;
659: }
660: }
661:
662: if (!isset($this->properties[$key])) {
663: if ($this->requireExtendsPropertiesClassReflectionExtension->hasProperty($this, $propertyName)) {
664: $property = $this->requireExtendsPropertiesClassReflectionExtension->getProperty($this, $propertyName);
665: $this->properties[$key] = $property;
666: }
667: }
668:
669: if (!isset($this->properties[$key])) {
670: throw new MissingPropertyFromReflectionException($this->getName(), $propertyName);
671: }
672:
673: return $this->properties[$key];
674: }
675:
676: public function hasNativeProperty(string $propertyName): bool
677: {
678: return $this->getPhpExtension()->hasProperty($this, $propertyName);
679: }
680:
681: public function getNativeProperty(string $propertyName): PhpPropertyReflection
682: {
683: if (!$this->hasNativeProperty($propertyName)) {
684: throw new MissingPropertyFromReflectionException($this->getName(), $propertyName);
685: }
686:
687: return $this->getPhpExtension()->getNativeProperty($this, $propertyName);
688: }
689:
690: public function isAbstract(): bool
691: {
692: return $this->reflection->isAbstract();
693: }
694:
695: public function isInterface(): bool
696: {
697: return $this->reflection->isInterface();
698: }
699:
700: public function isTrait(): bool
701: {
702: return $this->reflection->isTrait();
703: }
704:
705: /**
706: * @phpstan-assert-if-true ReflectionEnum $this->reflection
707: */
708: public function isEnum(): bool
709: {
710: return $this->reflection instanceof ReflectionEnum && $this->reflection->isEnum();
711: }
712:
713: /**
714: * @return 'Interface'|'Trait'|'Enum'|'Class'
715: */
716: public function getClassTypeDescription(): string
717: {
718: if ($this->isInterface()) {
719: return 'Interface';
720: } elseif ($this->isTrait()) {
721: return 'Trait';
722: } elseif ($this->isEnum()) {
723: return 'Enum';
724: }
725:
726: return 'Class';
727: }
728:
729: public function isReadOnly(): bool
730: {
731: return $this->reflection->isReadOnly();
732: }
733:
734: public function isBackedEnum(): bool
735: {
736: if (!$this->reflection instanceof ReflectionEnum) {
737: return false;
738: }
739:
740: return $this->reflection->isBacked();
741: }
742:
743: public function getBackedEnumType(): ?Type
744: {
745: if (!$this->reflection instanceof ReflectionEnum) {
746: return null;
747: }
748:
749: if (!$this->reflection->isBacked()) {
750: return null;
751: }
752:
753: return TypehintHelper::decideTypeFromReflection($this->reflection->getBackingType());
754: }
755:
756: public function hasEnumCase(string $name): bool
757: {
758: if (!$this->isEnum()) {
759: return false;
760: }
761:
762: return $this->reflection->hasCase($name);
763: }
764:
765: /**
766: * @return array<string, EnumCaseReflection>
767: */
768: public function getEnumCases(): array
769: {
770: if (!$this->isEnum()) {
771: throw new ShouldNotHappenException();
772: }
773:
774: if ($this->enumCases !== null) {
775: return $this->enumCases;
776: }
777:
778: $cases = [];
779: $initializerExprContext = InitializerExprContext::fromClassReflection($this);
780: foreach ($this->reflection->getCases() as $case) {
781: $valueType = null;
782: if ($case instanceof ReflectionEnumBackedCase) {
783: $valueType = $this->initializerExprTypeResolver->getType($case->getValueExpression(), $initializerExprContext);
784: }
785: /** @var string $caseName */
786: $caseName = $case->getName();
787: $cases[$caseName] = new EnumCaseReflection($this, $caseName, $valueType);
788: }
789:
790: return $this->enumCases = $cases;
791: }
792:
793: public function getEnumCase(string $name): EnumCaseReflection
794: {
795: if (!$this->hasEnumCase($name)) {
796: throw new ShouldNotHappenException(sprintf('Enum case %s::%s does not exist.', $this->getDisplayName(), $name));
797: }
798:
799: if (!$this->reflection instanceof ReflectionEnum) {
800: throw new ShouldNotHappenException();
801: }
802:
803: if ($this->enumCases !== null && array_key_exists($name, $this->enumCases)) {
804: return $this->enumCases[$name];
805: }
806:
807: $case = $this->reflection->getCase($name);
808: $valueType = null;
809: if ($case instanceof ReflectionEnumBackedCase) {
810: $valueType = $this->initializerExprTypeResolver->getType($case->getValueExpression(), InitializerExprContext::fromClassReflection($this));
811: }
812:
813: return new EnumCaseReflection($this, $name, $valueType);
814: }
815:
816: public function isClass(): bool
817: {
818: return !$this->isInterface() && !$this->isTrait() && !$this->isEnum();
819: }
820:
821: public function isAnonymous(): bool
822: {
823: return $this->anonymousFilename !== null;
824: }
825:
826: public function is(string $className): bool
827: {
828: return $this->getName() === $className || $this->isSubclassOf($className);
829: }
830:
831: public function isSubclassOf(string $className): bool
832: {
833: if (isset($this->subclasses[$className])) {
834: return $this->subclasses[$className];
835: }
836:
837: if (!$this->reflectionProvider->hasClass($className)) {
838: return $this->subclasses[$className] = false;
839: }
840:
841: try {
842: return $this->subclasses[$className] = $this->reflection->isSubclassOf($className);
843: } catch (ReflectionException) {
844: return $this->subclasses[$className] = false;
845: }
846: }
847:
848: public function implementsInterface(string $className): bool
849: {
850: try {
851: return $this->reflection->implementsInterface($className);
852: } catch (ReflectionException) {
853: return false;
854: }
855: }
856:
857: /**
858: * @return ClassReflection[]
859: */
860: public function getParents(): array
861: {
862: $parents = [];
863: $parent = $this->getParentClass();
864: while ($parent !== null) {
865: $parents[] = $parent;
866: $parent = $parent->getParentClass();
867: }
868:
869: return $parents;
870: }
871:
872: /**
873: * @return ClassReflection[]
874: */
875: public function getInterfaces(): array
876: {
877: if ($this->cachedInterfaces !== null) {
878: return $this->cachedInterfaces;
879: }
880:
881: $interfaces = $this->getImmediateInterfaces();
882: $immediateInterfaces = $interfaces;
883: $parent = $this->getParentClass();
884: while ($parent !== null) {
885: foreach ($parent->getImmediateInterfaces() as $parentInterface) {
886: $interfaces[$parentInterface->getName()] = $parentInterface;
887: foreach ($this->collectInterfaces($parentInterface) as $parentInterfaceInterface) {
888: $interfaces[$parentInterfaceInterface->getName()] = $parentInterfaceInterface;
889: }
890: }
891:
892: $parent = $parent->getParentClass();
893: }
894:
895: foreach ($immediateInterfaces as $immediateInterface) {
896: foreach ($this->collectInterfaces($immediateInterface) as $interfaceInterface) {
897: $interfaces[$interfaceInterface->getName()] = $interfaceInterface;
898: }
899: }
900:
901: $this->cachedInterfaces = $interfaces;
902:
903: return $interfaces;
904: }
905:
906: /**
907: * @return ClassReflection[]
908: */
909: private function collectInterfaces(ClassReflection $interface): array
910: {
911: $interfaces = [];
912: foreach ($interface->getImmediateInterfaces() as $immediateInterface) {
913: $interfaces[$immediateInterface->getName()] = $immediateInterface;
914: foreach ($this->collectInterfaces($immediateInterface) as $immediateInterfaceInterface) {
915: $interfaces[$immediateInterfaceInterface->getName()] = $immediateInterfaceInterface;
916: }
917: }
918:
919: return $interfaces;
920: }
921:
922: /**
923: * @return ClassReflection[]
924: */
925: public function getImmediateInterfaces(): array
926: {
927: $indirectInterfaceNames = [];
928: $parent = $this->getParentClass();
929: while ($parent !== null) {
930: foreach ($parent->getNativeReflection()->getInterfaceNames() as $parentInterfaceName) {
931: $indirectInterfaceNames[] = $parentInterfaceName;
932: }
933:
934: $parent = $parent->getParentClass();
935: }
936:
937: foreach ($this->getNativeReflection()->getInterfaces() as $interfaceInterface) {
938: foreach ($interfaceInterface->getInterfaceNames() as $interfaceInterfaceName) {
939: $indirectInterfaceNames[] = $interfaceInterfaceName;
940: }
941: }
942:
943: if ($this->reflection->isInterface()) {
944: $implementsTags = $this->getExtendsTags();
945: } else {
946: $implementsTags = $this->getImplementsTags();
947: }
948:
949: $immediateInterfaceNames = array_diff($this->getNativeReflection()->getInterfaceNames(), $indirectInterfaceNames);
950: $immediateInterfaces = [];
951: foreach ($immediateInterfaceNames as $immediateInterfaceName) {
952: if (!$this->reflectionProvider->hasClass($immediateInterfaceName)) {
953: continue;
954: }
955:
956: $immediateInterface = $this->reflectionProvider->getClass($immediateInterfaceName);
957: if (array_key_exists($immediateInterface->getName(), $implementsTags)) {
958: $implementsTag = $implementsTags[$immediateInterface->getName()];
959: $implementedType = $implementsTag->getType();
960: if ($this->isGeneric()) {
961: $implementedType = TemplateTypeHelper::resolveTemplateTypes(
962: $implementedType,
963: $this->getPossiblyIncompleteActiveTemplateTypeMap(),
964: $this->getCallSiteVarianceMap(),
965: TemplateTypeVariance::createStatic(),
966: true,
967: );
968: }
969:
970: if (
971: $implementedType instanceof GenericObjectType
972: && $implementedType->getClassReflection() !== null
973: ) {
974: $immediateInterfaces[$immediateInterface->getName()] = $implementedType->getClassReflection();
975: continue;
976: }
977: }
978:
979: if ($immediateInterface->isGeneric()) {
980: $immediateInterfaces[$immediateInterface->getName()] = $immediateInterface->withTypes(
981: array_values($immediateInterface->getTemplateTypeMap()->map(static fn (): Type => new ErrorType())->getTypes()),
982: );
983: continue;
984: }
985:
986: $immediateInterfaces[$immediateInterface->getName()] = $immediateInterface;
987: }
988:
989: return $immediateInterfaces;
990: }
991:
992: /**
993: * @return array<string, ClassReflection>
994: */
995: public function getTraits(bool $recursive = false): array
996: {
997: $traits = [];
998:
999: if ($recursive) {
1000: foreach ($this->collectTraits($this->getNativeReflection()) as $trait) {
1001: $traits[$trait->getName()] = $trait;
1002: }
1003: } else {
1004: $traits = $this->getNativeReflection()->getTraits();
1005: }
1006:
1007: $traits = array_map(fn (ReflectionClass $trait): ClassReflection => $this->reflectionProvider->getClass($trait->getName()), $traits);
1008:
1009: if ($recursive) {
1010: $parentClass = $this->getNativeReflection()->getParentClass();
1011:
1012: if ($parentClass !== false) {
1013: return array_merge(
1014: $traits,
1015: $this->reflectionProvider->getClass($parentClass->getName())->getTraits(true),
1016: );
1017: }
1018: }
1019:
1020: return $traits;
1021: }
1022:
1023: /**
1024: * @return list<class-string>
1025: */
1026: public function getParentClassesNames(): array
1027: {
1028: $parentNames = [];
1029: $parentClass = $this->getParentClass();
1030: while ($parentClass !== null) {
1031: $parentNames[] = $parentClass->getName();
1032: $parentClass = $parentClass->getParentClass();
1033: }
1034:
1035: return $parentNames;
1036: }
1037:
1038: public function hasConstant(string $name): bool
1039: {
1040: if (!$this->getNativeReflection()->hasConstant($name)) {
1041: return false;
1042: }
1043:
1044: $reflectionConstant = $this->getNativeReflection()->getReflectionConstant($name);
1045: if ($reflectionConstant === false) {
1046: return false;
1047: }
1048:
1049: return $this->reflectionProvider->hasClass($reflectionConstant->getDeclaringClass()->getName());
1050: }
1051:
1052: public function getConstant(string $name): ClassConstantReflection
1053: {
1054: if (!isset($this->constants[$name])) {
1055: $reflectionConstant = $this->getNativeReflection()->getReflectionConstant($name);
1056: if ($reflectionConstant === false) {
1057: throw new MissingConstantFromReflectionException($this->getName(), $name);
1058: }
1059:
1060: $declaringClass = $this->reflectionProvider->getClass($reflectionConstant->getDeclaringClass()->getName());
1061: $fileName = $declaringClass->getFileName();
1062: $phpDocType = null;
1063: $resolvedPhpDoc = $this->stubPhpDocProvider->findClassConstantPhpDoc(
1064: $declaringClass->getName(),
1065: $name,
1066: );
1067: if ($resolvedPhpDoc === null) {
1068: $docComment = null;
1069: if ($reflectionConstant->getDocComment() !== false) {
1070: $docComment = $reflectionConstant->getDocComment();
1071: }
1072: $resolvedPhpDoc = $this->phpDocInheritanceResolver->resolvePhpDocForConstant(
1073: $docComment,
1074: $declaringClass,
1075: $fileName,
1076: $name,
1077: );
1078: }
1079:
1080: $deprecatedDescription = $resolvedPhpDoc->getDeprecatedTag() !== null ? $resolvedPhpDoc->getDeprecatedTag()->getMessage() : null;
1081: $isDeprecated = $resolvedPhpDoc->isDeprecated();
1082: $isInternal = $resolvedPhpDoc->isInternal();
1083: $varTags = $resolvedPhpDoc->getVarTags();
1084: if (isset($varTags[0]) && count($varTags) === 1) {
1085: $phpDocType = $varTags[0]->getType();
1086: }
1087:
1088: $nativeType = null;
1089: if ($reflectionConstant->getType() !== null) {
1090: $nativeType = TypehintHelper::decideTypeFromReflection($reflectionConstant->getType(), null, $declaringClass);
1091: } elseif ($this->signatureMapProvider->hasClassConstantMetadata($declaringClass->getName(), $name)) {
1092: $nativeType = $this->signatureMapProvider->getClassConstantMetadata($declaringClass->getName(), $name)['nativeType'];
1093: }
1094:
1095: $this->constants[$name] = new ClassConstantReflection(
1096: $this->initializerExprTypeResolver,
1097: $declaringClass,
1098: $reflectionConstant,
1099: $nativeType,
1100: $phpDocType,
1101: $deprecatedDescription,
1102: $isDeprecated,
1103: $isInternal,
1104: );
1105: }
1106: return $this->constants[$name];
1107: }
1108:
1109: public function hasTraitUse(string $traitName): bool
1110: {
1111: return in_array($traitName, $this->getTraitNames(), true);
1112: }
1113:
1114: /**
1115: * @return string[]
1116: */
1117: private function getTraitNames(): array
1118: {
1119: $class = $this->reflection;
1120: $traitNames = array_map(static fn (ReflectionClass $class) => $class->getName(), $this->collectTraits($class));
1121: while ($class->getParentClass() !== false) {
1122: $traitNames = array_values(array_unique(array_merge($traitNames, $class->getParentClass()->getTraitNames())));
1123: $class = $class->getParentClass();
1124: }
1125:
1126: return $traitNames;
1127: }
1128:
1129: /**
1130: * @return array<string, TypeAlias>
1131: */
1132: public function getTypeAliases(): array
1133: {
1134: if ($this->typeAliases === null) {
1135: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1136: if ($resolvedPhpDoc === null) {
1137: return $this->typeAliases = [];
1138: }
1139:
1140: $typeAliasImportTags = $resolvedPhpDoc->getTypeAliasImportTags();
1141: $typeAliasTags = $resolvedPhpDoc->getTypeAliasTags();
1142:
1143: // prevent circular imports
1144: if (array_key_exists($this->getName(), self::$resolvingTypeAliasImports)) {
1145: throw new CircularTypeAliasDefinitionException();
1146: }
1147:
1148: self::$resolvingTypeAliasImports[$this->getName()] = true;
1149:
1150: $importedAliases = array_map(function (TypeAliasImportTag $typeAliasImportTag): ?TypeAlias {
1151: $importedAlias = $typeAliasImportTag->getImportedAlias();
1152: $importedFromClassName = $typeAliasImportTag->getImportedFrom();
1153:
1154: if (!$this->reflectionProvider->hasClass($importedFromClassName)) {
1155: return null;
1156: }
1157:
1158: $importedFromReflection = $this->reflectionProvider->getClass($importedFromClassName);
1159:
1160: try {
1161: $typeAliases = $importedFromReflection->getTypeAliases();
1162: } catch (CircularTypeAliasDefinitionException) {
1163: return TypeAlias::invalid();
1164: }
1165:
1166: if (!array_key_exists($importedAlias, $typeAliases)) {
1167: return null;
1168: }
1169:
1170: return $typeAliases[$importedAlias];
1171: }, $typeAliasImportTags);
1172:
1173: unset(self::$resolvingTypeAliasImports[$this->getName()]);
1174:
1175: $localAliases = array_map(static fn (TypeAliasTag $typeAliasTag): TypeAlias => $typeAliasTag->getTypeAlias(), $typeAliasTags);
1176:
1177: $this->typeAliases = array_filter(
1178: array_merge($importedAliases, $localAliases),
1179: static fn (?TypeAlias $typeAlias): bool => $typeAlias !== null,
1180: );
1181: }
1182:
1183: return $this->typeAliases;
1184: }
1185:
1186: public function getDeprecatedDescription(): ?string
1187: {
1188: if ($this->deprecatedDescription === null && $this->isDeprecated()) {
1189: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1190: if ($resolvedPhpDoc !== null && $resolvedPhpDoc->getDeprecatedTag() !== null) {
1191: $this->deprecatedDescription = $resolvedPhpDoc->getDeprecatedTag()->getMessage();
1192: }
1193: }
1194:
1195: return $this->deprecatedDescription;
1196: }
1197:
1198: public function isDeprecated(): bool
1199: {
1200: if ($this->isDeprecated === null) {
1201: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1202: $this->isDeprecated = $resolvedPhpDoc !== null && $resolvedPhpDoc->isDeprecated();
1203: }
1204:
1205: return $this->isDeprecated;
1206: }
1207:
1208: public function isBuiltin(): bool
1209: {
1210: return $this->reflection->isInternal();
1211: }
1212:
1213: public function isInternal(): bool
1214: {
1215: if ($this->isInternal === null) {
1216: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1217: $this->isInternal = $resolvedPhpDoc !== null && $resolvedPhpDoc->isInternal();
1218: }
1219:
1220: return $this->isInternal;
1221: }
1222:
1223: public function isFinal(): bool
1224: {
1225: if ($this->isFinalByKeyword()) {
1226: return true;
1227: }
1228:
1229: if ($this->isFinal === null) {
1230: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1231: $this->isFinal = $resolvedPhpDoc !== null && $resolvedPhpDoc->isFinal();
1232: }
1233:
1234: return $this->isFinal;
1235: }
1236:
1237: public function isImmutable(): bool
1238: {
1239: if ($this->isImmutable === null) {
1240: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1241: $this->isImmutable = $resolvedPhpDoc !== null && $resolvedPhpDoc->isImmutable();
1242:
1243: $parentClass = $this->getParentClass();
1244: if ($parentClass !== null && !$this->isImmutable) {
1245: $this->isImmutable = $parentClass->isImmutable();
1246: }
1247: }
1248:
1249: return $this->isImmutable;
1250: }
1251:
1252: public function hasConsistentConstructor(): bool
1253: {
1254: if ($this->hasConsistentConstructor === null) {
1255: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1256: $this->hasConsistentConstructor = $resolvedPhpDoc !== null && $resolvedPhpDoc->hasConsistentConstructor();
1257: }
1258:
1259: return $this->hasConsistentConstructor;
1260: }
1261:
1262: public function acceptsNamedArguments(): bool
1263: {
1264: if ($this->acceptsNamedArguments === null) {
1265: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1266: $this->acceptsNamedArguments = $resolvedPhpDoc === null || $resolvedPhpDoc->acceptsNamedArguments();
1267: }
1268:
1269: return $this->acceptsNamedArguments;
1270: }
1271:
1272: public function isFinalByKeyword(): bool
1273: {
1274: if ($this->isAnonymous()) {
1275: return true;
1276: }
1277:
1278: return $this->reflection->isFinal();
1279: }
1280:
1281: public function isAttributeClass(): bool
1282: {
1283: return $this->findAttributeFlags() !== null;
1284: }
1285:
1286: private function findAttributeFlags(): ?int
1287: {
1288: if ($this->isInterface() || $this->isTrait() || $this->isEnum()) {
1289: return null;
1290: }
1291:
1292: $nativeAttributes = $this->reflection->getAttributes(Attribute::class);
1293: if (count($nativeAttributes) === 1) {
1294: if (!$this->reflectionProvider->hasClass(Attribute::class)) {
1295: return null;
1296: }
1297:
1298: $attributeClass = $this->reflectionProvider->getClass(Attribute::class);
1299: $arguments = [];
1300: foreach ($nativeAttributes[0]->getArgumentsExpressions() as $i => $expression) {
1301: $arguments[] = new Arg($expression, false, false, [], is_int($i) ? null : new Identifier($i));
1302: }
1303:
1304: if (!$attributeClass->hasConstructor()) {
1305: return null;
1306: }
1307: $attributeConstructor = $attributeClass->getConstructor();
1308: $attributeConstructorVariant = ParametersAcceptorSelector::selectSingle($attributeConstructor->getVariants());
1309:
1310: if (count($arguments) === 0) {
1311: $flagType = $attributeConstructorVariant->getParameters()[0]->getDefaultValue();
1312: } else {
1313: $staticCall = ArgumentsNormalizer::reorderStaticCallArguments(
1314: $attributeConstructorVariant,
1315: new StaticCall(new FullyQualified(Attribute::class), $attributeConstructor->getName(), $arguments),
1316: );
1317: if ($staticCall === null) {
1318: return null;
1319: }
1320: $flagExpr = $staticCall->getArgs()[0]->value;
1321: $flagType = $this->initializerExprTypeResolver->getType($flagExpr, InitializerExprContext::fromClassReflection($this));
1322: }
1323:
1324: if (!$flagType instanceof ConstantIntegerType) {
1325: return null;
1326: }
1327:
1328: return $flagType->getValue();
1329: }
1330:
1331: return null;
1332: }
1333:
1334: public function getAttributeClassFlags(): int
1335: {
1336: $flags = $this->findAttributeFlags();
1337: if ($flags === null) {
1338: throw new ShouldNotHappenException();
1339: }
1340:
1341: return $flags;
1342: }
1343:
1344: public function getTemplateTypeMap(): TemplateTypeMap
1345: {
1346: if ($this->templateTypeMap !== null) {
1347: return $this->templateTypeMap;
1348: }
1349:
1350: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1351: if ($resolvedPhpDoc === null) {
1352: $this->templateTypeMap = TemplateTypeMap::createEmpty();
1353: return $this->templateTypeMap;
1354: }
1355:
1356: $templateTypeScope = TemplateTypeScope::createWithClass($this->getName());
1357:
1358: $templateTypeMap = new TemplateTypeMap(array_map(static fn (TemplateTag $tag): Type => TemplateTypeFactory::fromTemplateTag($templateTypeScope, $tag), $this->getTemplateTags()));
1359:
1360: $this->templateTypeMap = $templateTypeMap;
1361:
1362: return $templateTypeMap;
1363: }
1364:
1365: public function getActiveTemplateTypeMap(): TemplateTypeMap
1366: {
1367: if ($this->activeTemplateTypeMap !== null) {
1368: return $this->activeTemplateTypeMap;
1369: }
1370: $resolved = $this->resolvedTemplateTypeMap;
1371: if ($resolved !== null) {
1372: $templateTypeMap = $this->getTemplateTypeMap();
1373: return $this->activeTemplateTypeMap = $resolved->map(static function (string $name, Type $type) use ($templateTypeMap): Type {
1374: if ($type instanceof ErrorType) {
1375: $templateType = $templateTypeMap->getType($name);
1376: if ($templateType !== null) {
1377: return TemplateTypeHelper::resolveToBounds($templateType);
1378: }
1379: }
1380:
1381: return $type;
1382: });
1383: }
1384:
1385: return $this->activeTemplateTypeMap = $this->getTemplateTypeMap();
1386: }
1387:
1388: public function getPossiblyIncompleteActiveTemplateTypeMap(): TemplateTypeMap
1389: {
1390: return $this->resolvedTemplateTypeMap ?? $this->getTemplateTypeMap();
1391: }
1392:
1393: private function getDefaultCallSiteVarianceMap(): TemplateTypeVarianceMap
1394: {
1395: if ($this->defaultCallSiteVarianceMap !== null) {
1396: return $this->defaultCallSiteVarianceMap;
1397: }
1398:
1399: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1400: if ($resolvedPhpDoc === null) {
1401: $this->defaultCallSiteVarianceMap = TemplateTypeVarianceMap::createEmpty();
1402: return $this->defaultCallSiteVarianceMap;
1403: }
1404:
1405: $map = [];
1406: foreach ($this->getTemplateTags() as $templateTag) {
1407: $map[$templateTag->getName()] = TemplateTypeVariance::createInvariant();
1408: }
1409:
1410: $this->defaultCallSiteVarianceMap = new TemplateTypeVarianceMap($map);
1411: return $this->defaultCallSiteVarianceMap;
1412: }
1413:
1414: public function getCallSiteVarianceMap(): TemplateTypeVarianceMap
1415: {
1416: return $this->callSiteVarianceMap ??= $this->resolvedCallSiteVarianceMap ?? $this->getDefaultCallSiteVarianceMap();
1417: }
1418:
1419: public function isGeneric(): bool
1420: {
1421: if ($this->isGeneric === null) {
1422: if ($this->isEnum()) {
1423: return $this->isGeneric = false;
1424: }
1425:
1426: $this->isGeneric = count($this->getTemplateTags()) > 0;
1427: }
1428:
1429: return $this->isGeneric;
1430: }
1431:
1432: /**
1433: * @param array<int, Type> $types
1434: */
1435: public function typeMapFromList(array $types): TemplateTypeMap
1436: {
1437: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1438: if ($resolvedPhpDoc === null) {
1439: return TemplateTypeMap::createEmpty();
1440: }
1441:
1442: $map = [];
1443: $i = 0;
1444: foreach ($resolvedPhpDoc->getTemplateTags() as $tag) {
1445: $map[$tag->getName()] = $types[$i] ?? $tag->getBound();
1446: $i++;
1447: }
1448:
1449: return new TemplateTypeMap($map);
1450: }
1451:
1452: /**
1453: * @param array<int, TemplateTypeVariance> $variances
1454: */
1455: public function varianceMapFromList(array $variances): TemplateTypeVarianceMap
1456: {
1457: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1458: if ($resolvedPhpDoc === null) {
1459: return new TemplateTypeVarianceMap([]);
1460: }
1461:
1462: $map = [];
1463: $i = 0;
1464: foreach ($resolvedPhpDoc->getTemplateTags() as $tag) {
1465: $map[$tag->getName()] = $variances[$i] ?? TemplateTypeVariance::createInvariant();
1466: $i++;
1467: }
1468:
1469: return new TemplateTypeVarianceMap($map);
1470: }
1471:
1472: /** @return array<int, Type> */
1473: public function typeMapToList(TemplateTypeMap $typeMap): array
1474: {
1475: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1476: if ($resolvedPhpDoc === null) {
1477: return [];
1478: }
1479:
1480: $list = [];
1481: foreach ($resolvedPhpDoc->getTemplateTags() as $tag) {
1482: $list[] = $typeMap->getType($tag->getName()) ?? $tag->getBound();
1483: }
1484:
1485: return $list;
1486: }
1487:
1488: /** @return array<int, TemplateTypeVariance> */
1489: public function varianceMapToList(TemplateTypeVarianceMap $varianceMap): array
1490: {
1491: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1492: if ($resolvedPhpDoc === null) {
1493: return [];
1494: }
1495:
1496: $list = [];
1497: foreach ($resolvedPhpDoc->getTemplateTags() as $tag) {
1498: $list[] = $varianceMap->getVariance($tag->getName()) ?? TemplateTypeVariance::createInvariant();
1499: }
1500:
1501: return $list;
1502: }
1503:
1504: /**
1505: * @param array<int, Type> $types
1506: */
1507: public function withTypes(array $types): self
1508: {
1509: return new self(
1510: $this->reflectionProvider,
1511: $this->initializerExprTypeResolver,
1512: $this->fileTypeMapper,
1513: $this->stubPhpDocProvider,
1514: $this->phpDocInheritanceResolver,
1515: $this->phpVersion,
1516: $this->signatureMapProvider,
1517: $this->propertiesClassReflectionExtensions,
1518: $this->methodsClassReflectionExtensions,
1519: $this->allowedSubTypesClassReflectionExtensions,
1520: $this->requireExtendsPropertiesClassReflectionExtension,
1521: $this->requireExtendsMethodsClassReflectionExtension,
1522: $this->displayName,
1523: $this->reflection,
1524: $this->anonymousFilename,
1525: $this->typeMapFromList($types),
1526: $this->stubPhpDocBlock,
1527: $this->universalObjectCratesClasses,
1528: null,
1529: $this->resolvedCallSiteVarianceMap,
1530: );
1531: }
1532:
1533: /**
1534: * @param array<int, TemplateTypeVariance> $variances
1535: */
1536: public function withVariances(array $variances): self
1537: {
1538: return new self(
1539: $this->reflectionProvider,
1540: $this->initializerExprTypeResolver,
1541: $this->fileTypeMapper,
1542: $this->stubPhpDocProvider,
1543: $this->phpDocInheritanceResolver,
1544: $this->phpVersion,
1545: $this->signatureMapProvider,
1546: $this->propertiesClassReflectionExtensions,
1547: $this->methodsClassReflectionExtensions,
1548: $this->allowedSubTypesClassReflectionExtensions,
1549: $this->requireExtendsPropertiesClassReflectionExtension,
1550: $this->requireExtendsMethodsClassReflectionExtension,
1551: $this->displayName,
1552: $this->reflection,
1553: $this->anonymousFilename,
1554: $this->resolvedTemplateTypeMap,
1555: $this->stubPhpDocBlock,
1556: $this->universalObjectCratesClasses,
1557: null,
1558: $this->varianceMapFromList($variances),
1559: );
1560: }
1561:
1562: public function getResolvedPhpDoc(): ?ResolvedPhpDocBlock
1563: {
1564: if ($this->stubPhpDocBlock !== null) {
1565: return $this->stubPhpDocBlock;
1566: }
1567:
1568: $fileName = $this->getFileName();
1569: if (is_bool($this->reflectionDocComment)) {
1570: $docComment = $this->reflection->getDocComment();
1571: $this->reflectionDocComment = $docComment !== false ? $docComment : null;
1572: }
1573:
1574: if ($this->reflectionDocComment === null) {
1575: return null;
1576: }
1577:
1578: if ($this->resolvedPhpDocBlock !== false) {
1579: return $this->resolvedPhpDocBlock;
1580: }
1581:
1582: return $this->resolvedPhpDocBlock = $this->fileTypeMapper->getResolvedPhpDoc($fileName, $this->getName(), null, null, $this->reflectionDocComment);
1583: }
1584:
1585: public function getTraitContextResolvedPhpDoc(self $implementingClass): ?ResolvedPhpDocBlock
1586: {
1587: if (!$this->isTrait()) {
1588: throw new ShouldNotHappenException();
1589: }
1590: if ($implementingClass->isTrait()) {
1591: throw new ShouldNotHappenException();
1592: }
1593: $fileName = $this->getFileName();
1594: if (is_bool($this->reflectionDocComment)) {
1595: $docComment = $this->reflection->getDocComment();
1596: $this->reflectionDocComment = $docComment !== false ? $docComment : null;
1597: }
1598:
1599: if ($this->reflectionDocComment === null) {
1600: return null;
1601: }
1602:
1603: if ($this->traitContextResolvedPhpDocBlock !== false) {
1604: return $this->traitContextResolvedPhpDocBlock;
1605: }
1606:
1607: return $this->traitContextResolvedPhpDocBlock = $this->fileTypeMapper->getResolvedPhpDoc($fileName, $implementingClass->getName(), $this->getName(), null, $this->reflectionDocComment);
1608: }
1609:
1610: private function getFirstExtendsTag(): ?ExtendsTag
1611: {
1612: foreach ($this->getExtendsTags() as $tag) {
1613: return $tag;
1614: }
1615:
1616: return null;
1617: }
1618:
1619: /** @return array<string, ExtendsTag> */
1620: public function getExtendsTags(): array
1621: {
1622: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1623: if ($resolvedPhpDoc === null) {
1624: return [];
1625: }
1626:
1627: return $resolvedPhpDoc->getExtendsTags();
1628: }
1629:
1630: /** @return array<string, ImplementsTag> */
1631: public function getImplementsTags(): array
1632: {
1633: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1634: if ($resolvedPhpDoc === null) {
1635: return [];
1636: }
1637:
1638: return $resolvedPhpDoc->getImplementsTags();
1639: }
1640:
1641: /** @return array<string,TemplateTag> */
1642: public function getTemplateTags(): array
1643: {
1644: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1645: if ($resolvedPhpDoc === null) {
1646: return [];
1647: }
1648:
1649: return $resolvedPhpDoc->getTemplateTags();
1650: }
1651:
1652: /**
1653: * @return array<string,ClassReflection>
1654: */
1655: public function getAncestors(): array
1656: {
1657: $ancestors = $this->ancestors;
1658:
1659: if ($ancestors === null) {
1660: $ancestors = [
1661: $this->getName() => $this,
1662: ];
1663:
1664: $addToAncestors = static function (string $name, ClassReflection $classReflection) use (&$ancestors): void {
1665: if (array_key_exists($name, $ancestors)) {
1666: return;
1667: }
1668:
1669: $ancestors[$name] = $classReflection;
1670: };
1671:
1672: foreach ($this->getInterfaces() as $interface) {
1673: $addToAncestors($interface->getName(), $interface);
1674: foreach ($interface->getAncestors() as $name => $ancestor) {
1675: $addToAncestors($name, $ancestor);
1676: }
1677: }
1678:
1679: foreach ($this->getTraits() as $trait) {
1680: $addToAncestors($trait->getName(), $trait);
1681: foreach ($trait->getAncestors() as $name => $ancestor) {
1682: $addToAncestors($name, $ancestor);
1683: }
1684: }
1685:
1686: $parent = $this->getParentClass();
1687: if ($parent !== null) {
1688: $addToAncestors($parent->getName(), $parent);
1689: foreach ($parent->getAncestors() as $name => $ancestor) {
1690: $addToAncestors($name, $ancestor);
1691: }
1692: }
1693:
1694: $this->ancestors = $ancestors;
1695: }
1696:
1697: return $ancestors;
1698: }
1699:
1700: public function getAncestorWithClassName(string $className): ?self
1701: {
1702: return $this->getAncestors()[$className] ?? null;
1703: }
1704:
1705: /**
1706: * @param string[] $ancestorClasses
1707: */
1708: private function isValidAncestorType(Type $type, array $ancestorClasses): bool
1709: {
1710: if (!$type instanceof GenericObjectType) {
1711: return false;
1712: }
1713:
1714: $reflection = $type->getClassReflection();
1715: if ($reflection === null) {
1716: return false;
1717: }
1718:
1719: return in_array($reflection->getName(), $ancestorClasses, true);
1720: }
1721:
1722: /**
1723: * @return array<MixinTag>
1724: */
1725: public function getMixinTags(): array
1726: {
1727: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1728: if ($resolvedPhpDoc === null) {
1729: return [];
1730: }
1731:
1732: return $resolvedPhpDoc->getMixinTags();
1733: }
1734:
1735: /**
1736: * @return array<RequireExtendsTag>
1737: */
1738: public function getRequireExtendsTags(): array
1739: {
1740: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1741: if ($resolvedPhpDoc === null) {
1742: return [];
1743: }
1744:
1745: return $resolvedPhpDoc->getRequireExtendsTags();
1746: }
1747:
1748: /**
1749: * @return array<RequireImplementsTag>
1750: */
1751: public function getRequireImplementsTags(): array
1752: {
1753: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1754: if ($resolvedPhpDoc === null) {
1755: return [];
1756: }
1757:
1758: return $resolvedPhpDoc->getRequireImplementsTags();
1759: }
1760:
1761: /**
1762: * @return array<string, PropertyTag>
1763: */
1764: public function getPropertyTags(): array
1765: {
1766: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1767: if ($resolvedPhpDoc === null) {
1768: return [];
1769: }
1770:
1771: return $resolvedPhpDoc->getPropertyTags();
1772: }
1773:
1774: /**
1775: * @return array<string, MethodTag>
1776: */
1777: public function getMethodTags(): array
1778: {
1779: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1780: if ($resolvedPhpDoc === null) {
1781: return [];
1782: }
1783:
1784: return $resolvedPhpDoc->getMethodTags();
1785: }
1786:
1787: /**
1788: * @return array<Type>
1789: */
1790: public function getResolvedMixinTypes(): array
1791: {
1792: $types = [];
1793: foreach ($this->getMixinTags() as $mixinTag) {
1794: if (!$this->isGeneric()) {
1795: $types[] = $mixinTag->getType();
1796: continue;
1797: }
1798:
1799: $types[] = TemplateTypeHelper::resolveTemplateTypes(
1800: $mixinTag->getType(),
1801: $this->getActiveTemplateTypeMap(),
1802: $this->getCallSiteVarianceMap(),
1803: TemplateTypeVariance::createStatic(),
1804: );
1805: }
1806:
1807: return $types;
1808: }
1809:
1810: /**
1811: * @return array<Type>|null
1812: */
1813: public function getAllowedSubTypes(): ?array
1814: {
1815: foreach ($this->allowedSubTypesClassReflectionExtensions as $allowedSubTypesClassReflectionExtension) {
1816: if ($allowedSubTypesClassReflectionExtension->supports($this)) {
1817: return $allowedSubTypesClassReflectionExtension->getAllowedSubTypes($this);
1818: }
1819: }
1820:
1821: return null;
1822: }
1823:
1824: }
1825: