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