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