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