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