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