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