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 AttributeReflectionFactory $attributeReflectionFactory,
159: private array $propertiesClassReflectionExtensions,
160: private array $methodsClassReflectionExtensions,
161: private array $allowedSubTypesClassReflectionExtensions,
162: private RequireExtendsPropertiesClassReflectionExtension $requireExtendsPropertiesClassReflectionExtension,
163: private RequireExtendsMethodsClassReflectionExtension $requireExtendsMethodsClassReflectionExtension,
164: private string $displayName,
165: private ReflectionClass|ReflectionEnum $reflection,
166: private ?string $anonymousFilename,
167: private ?TemplateTypeMap $resolvedTemplateTypeMap,
168: private ?ResolvedPhpDocBlock $stubPhpDocBlock,
169: private array $universalObjectCratesClasses,
170: private ?string $extraCacheKey = null,
171: private ?TemplateTypeVarianceMap $resolvedCallSiteVarianceMap = null,
172: )
173: {
174: }
175:
176: public function getNativeReflection(): ReflectionClass|ReflectionEnum
177: {
178: return $this->reflection;
179: }
180:
181: public function getFileName(): ?string
182: {
183: if (!is_bool($this->filename)) {
184: return $this->filename;
185: }
186:
187: if ($this->anonymousFilename !== null) {
188: return $this->filename = $this->anonymousFilename;
189: }
190: $fileName = $this->reflection->getFileName();
191: if ($fileName === false) {
192: return $this->filename = null;
193: }
194:
195: if (!is_file($fileName)) {
196: return $this->filename = null;
197: }
198:
199: return $this->filename = $fileName;
200: }
201:
202: public function getParentClass(): ?ClassReflection
203: {
204: if (!is_bool($this->cachedParentClass)) {
205: return $this->cachedParentClass;
206: }
207:
208: $parentClass = $this->reflection->getParentClass();
209:
210: if ($parentClass === false) {
211: return $this->cachedParentClass = null;
212: }
213:
214: $extendsTag = $this->getFirstExtendsTag();
215:
216: if ($extendsTag !== null && $this->isValidAncestorType($extendsTag->getType(), [$parentClass->getName()])) {
217: $extendedType = $extendsTag->getType();
218:
219: if ($this->isGeneric()) {
220: $extendedType = TemplateTypeHelper::resolveTemplateTypes(
221: $extendedType,
222: $this->getPossiblyIncompleteActiveTemplateTypeMap(),
223: $this->getCallSiteVarianceMap(),
224: TemplateTypeVariance::createStatic(),
225: );
226: }
227:
228: if (!$extendedType instanceof GenericObjectType) {
229: return $this->reflectionProvider->getClass($parentClass->getName());
230: }
231:
232: return $extendedType->getClassReflection() ?? $this->reflectionProvider->getClass($parentClass->getName());
233: }
234:
235: $parentReflection = $this->reflectionProvider->getClass($parentClass->getName());
236: if ($parentReflection->isGeneric()) {
237: return $parentReflection->withTypes(
238: array_values($parentReflection->getTemplateTypeMap()->map(static fn (): Type => new ErrorType())->getTypes()),
239: );
240: }
241:
242: $this->cachedParentClass = $parentReflection;
243:
244: return $parentReflection;
245: }
246:
247: /**
248: * @return class-string
249: */
250: public function getName(): string
251: {
252: return $this->reflection->getName();
253: }
254:
255: public function getDisplayName(bool $withTemplateTypes = true): string
256: {
257: if (
258: $withTemplateTypes === false
259: || $this->resolvedTemplateTypeMap === null
260: || count($this->resolvedTemplateTypeMap->getTypes()) === 0
261: ) {
262: return $this->displayName;
263: }
264:
265: $templateTypes = [];
266: $variances = $this->getCallSiteVarianceMap()->getVariances();
267: foreach ($this->getActiveTemplateTypeMap()->getTypes() as $name => $templateType) {
268: $variance = $variances[$name] ?? null;
269: if ($variance === null) {
270: continue;
271: }
272:
273: $templateTypes[] = TypeProjectionHelper::describe($templateType, $variance, VerbosityLevel::typeOnly());
274: }
275:
276: return $this->displayName . '<' . implode(',', $templateTypes) . '>';
277: }
278:
279: public function getCacheKey(): string
280: {
281: $cacheKey = $this->cacheKey;
282: if ($cacheKey !== null) {
283: return $this->cacheKey;
284: }
285:
286: $cacheKey = $this->displayName;
287:
288: if ($this->resolvedTemplateTypeMap !== null) {
289: $templateTypes = [];
290: $variances = $this->getCallSiteVarianceMap()->getVariances();
291: foreach ($this->getActiveTemplateTypeMap()->getTypes() as $name => $templateType) {
292: $variance = $variances[$name] ?? null;
293: if ($variance === null) {
294: continue;
295: }
296:
297: $templateTypes[] = TypeProjectionHelper::describe($templateType, $variance, VerbosityLevel::cache());
298: }
299:
300: $cacheKey .= '<' . implode(',', $templateTypes) . '>';
301: }
302:
303: if ($this->extraCacheKey !== null) {
304: $cacheKey .= '-' . $this->extraCacheKey;
305: }
306:
307: $this->cacheKey = $cacheKey;
308:
309: return $cacheKey;
310: }
311:
312: /**
313: * @return int[]
314: */
315: public function getClassHierarchyDistances(): array
316: {
317: if ($this->classHierarchyDistances === null) {
318: $distance = 0;
319: $distances = [
320: $this->getName() => $distance,
321: ];
322: $currentClassReflection = $this->getNativeReflection();
323: foreach ($this->collectTraits($this->getNativeReflection()) as $trait) {
324: $distance++;
325: if (array_key_exists($trait->getName(), $distances)) {
326: continue;
327: }
328:
329: $distances[$trait->getName()] = $distance;
330: }
331:
332: while ($currentClassReflection->getParentClass() !== false) {
333: $distance++;
334: $parentClassName = $currentClassReflection->getParentClass()->getName();
335: if (!array_key_exists($parentClassName, $distances)) {
336: $distances[$parentClassName] = $distance;
337: }
338: $currentClassReflection = $currentClassReflection->getParentClass();
339: foreach ($this->collectTraits($currentClassReflection) as $trait) {
340: $distance++;
341: if (array_key_exists($trait->getName(), $distances)) {
342: continue;
343: }
344:
345: $distances[$trait->getName()] = $distance;
346: }
347: }
348: foreach ($this->getNativeReflection()->getInterfaces() as $interface) {
349: $distance++;
350: if (array_key_exists($interface->getName(), $distances)) {
351: continue;
352: }
353:
354: $distances[$interface->getName()] = $distance;
355: }
356:
357: $this->classHierarchyDistances = $distances;
358: }
359:
360: return $this->classHierarchyDistances;
361: }
362:
363: /**
364: * @return list<ReflectionClass>
365: */
366: private function collectTraits(ReflectionClass|ReflectionEnum $class): array
367: {
368: $traits = [];
369: $traitsLeftToAnalyze = $class->getTraits();
370:
371: while (count($traitsLeftToAnalyze) !== 0) {
372: $trait = reset($traitsLeftToAnalyze);
373: $traits[] = $trait;
374:
375: foreach ($trait->getTraits() as $subTrait) {
376: if (in_array($subTrait, $traits, true)) {
377: continue;
378: }
379:
380: $traitsLeftToAnalyze[] = $subTrait;
381: }
382:
383: array_shift($traitsLeftToAnalyze);
384: }
385:
386: return $traits;
387: }
388:
389: public function allowsDynamicProperties(): bool
390: {
391: if ($this->isEnum()) {
392: return false;
393: }
394:
395: if (!$this->phpVersion->deprecatesDynamicProperties()) {
396: return true;
397: }
398:
399: if ($this->isReadOnly()) {
400: return false;
401: }
402:
403: if (UniversalObjectCratesClassReflectionExtension::isUniversalObjectCrate(
404: $this->reflectionProvider,
405: $this,
406: )) {
407: return true;
408: }
409:
410: $class = $this;
411: $attributes = $class->reflection->getAttributes('AllowDynamicProperties');
412: while (count($attributes) === 0 && $class->getParentClass() !== null) {
413: $attributes = $class->getParentClass()->reflection->getAttributes('AllowDynamicProperties');
414: $class = $class->getParentClass();
415: }
416:
417: return count($attributes) > 0;
418: }
419:
420: private function allowsDynamicPropertiesExtensions(): bool
421: {
422: if ($this->allowsDynamicProperties()) {
423: return true;
424: }
425:
426: $hasMagicMethod = $this->hasNativeMethod('__get') || $this->hasNativeMethod('__set') || $this->hasNativeMethod('__isset');
427: if ($hasMagicMethod) {
428: return true;
429: }
430:
431: foreach ($this->getRequireExtendsTags() as $extendsTag) {
432: $type = $extendsTag->getType();
433: if (!$type instanceof ObjectType) {
434: continue;
435: }
436:
437: $reflection = $type->getClassReflection();
438: if ($reflection === null) {
439: continue;
440: }
441:
442: if (!$reflection->allowsDynamicPropertiesExtensions()) {
443: continue;
444: }
445:
446: return true;
447: }
448:
449: return false;
450: }
451:
452: public function hasProperty(string $propertyName): bool
453: {
454: if ($this->isEnum()) {
455: return $this->hasNativeProperty($propertyName);
456: }
457:
458: foreach ($this->propertiesClassReflectionExtensions as $i => $extension) {
459: if ($i > 0 && !$this->allowsDynamicPropertiesExtensions()) {
460: break;
461: }
462: if ($extension->hasProperty($this, $propertyName)) {
463: return true;
464: }
465: }
466:
467: if ($this->requireExtendsPropertiesClassReflectionExtension->hasProperty($this, $propertyName)) {
468: return true;
469: }
470:
471: return false;
472: }
473:
474: public function hasMethod(string $methodName): bool
475: {
476: foreach ($this->methodsClassReflectionExtensions as $extension) {
477: if ($extension->hasMethod($this, $methodName)) {
478: return true;
479: }
480: }
481:
482: if ($this->requireExtendsMethodsClassReflectionExtension->hasMethod($this, $methodName)) {
483: return true;
484: }
485:
486: return false;
487: }
488:
489: public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): ExtendedMethodReflection
490: {
491: $key = $methodName;
492: if ($scope->isInClass()) {
493: $key = sprintf('%s-%s', $key, $scope->getClassReflection()->getCacheKey());
494: }
495:
496: if (!isset($this->methods[$key])) {
497: foreach ($this->methodsClassReflectionExtensions as $extension) {
498: if (!$extension->hasMethod($this, $methodName)) {
499: continue;
500: }
501:
502: $method = $this->wrapExtendedMethod($extension->getMethod($this, $methodName));
503: if ($scope->canCallMethod($method)) {
504: return $this->methods[$key] = $method;
505: }
506: $this->methods[$key] = $method;
507: }
508: }
509:
510: if (!isset($this->methods[$key])) {
511: if ($this->requireExtendsMethodsClassReflectionExtension->hasMethod($this, $methodName)) {
512: $method = $this->requireExtendsMethodsClassReflectionExtension->getMethod($this, $methodName);
513: $this->methods[$key] = $method;
514: }
515: }
516:
517: if (!isset($this->methods[$key])) {
518: throw new MissingMethodFromReflectionException($this->getName(), $methodName);
519: }
520:
521: return $this->methods[$key];
522: }
523:
524: private function wrapExtendedMethod(MethodReflection $method): ExtendedMethodReflection
525: {
526: if ($method instanceof ExtendedMethodReflection) {
527: return $method;
528: }
529:
530: return new WrappedExtendedMethodReflection($method);
531: }
532:
533: private function wrapExtendedProperty(PropertyReflection $method): ExtendedPropertyReflection
534: {
535: if ($method instanceof ExtendedPropertyReflection) {
536: return $method;
537: }
538:
539: return new WrappedExtendedPropertyReflection($method);
540: }
541:
542: public function hasNativeMethod(string $methodName): bool
543: {
544: return $this->getPhpExtension()->hasNativeMethod($this, $methodName);
545: }
546:
547: public function getNativeMethod(string $methodName): ExtendedMethodReflection
548: {
549: if (!$this->hasNativeMethod($methodName)) {
550: throw new MissingMethodFromReflectionException($this->getName(), $methodName);
551: }
552: return $this->getPhpExtension()->getNativeMethod($this, $methodName);
553: }
554:
555: public function hasConstructor(): bool
556: {
557: return $this->findConstructor() !== null;
558: }
559:
560: public function getConstructor(): ExtendedMethodReflection
561: {
562: $constructor = $this->findConstructor();
563: if ($constructor === null) {
564: throw new ShouldNotHappenException();
565: }
566: return $this->getNativeMethod($constructor->getName());
567: }
568:
569: private function findConstructor(): ?ReflectionMethod
570: {
571: $constructor = $this->reflection->getConstructor();
572: if ($constructor === null) {
573: return null;
574: }
575:
576: if ($this->phpVersion->supportsLegacyConstructor()) {
577: return $constructor;
578: }
579:
580: if (strtolower($constructor->getName()) !== '__construct') {
581: return null;
582: }
583:
584: return $constructor;
585: }
586:
587: private function getPhpExtension(): PhpClassReflectionExtension
588: {
589: $extension = $this->methodsClassReflectionExtensions[0];
590: if (!$extension instanceof PhpClassReflectionExtension) {
591: throw new ShouldNotHappenException();
592: }
593:
594: return $extension;
595: }
596:
597: /** @internal */
598: public function evictPrivateSymbols(): void
599: {
600: foreach ($this->constants as $name => $constant) {
601: if (!$constant->isPrivate()) {
602: continue;
603: }
604:
605: unset($this->constants[$name]);
606: }
607: foreach ($this->properties as $name => $property) {
608: if (!$property->isPrivate()) {
609: continue;
610: }
611:
612: unset($this->properties[$name]);
613: }
614: foreach ($this->methods as $name => $method) {
615: if (!$method->isPrivate()) {
616: continue;
617: }
618:
619: unset($this->methods[$name]);
620: }
621: $this->getPhpExtension()->evictPrivateSymbols($this->getCacheKey());
622: }
623:
624: public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
625: {
626: if ($this->isEnum()) {
627: return $this->getNativeProperty($propertyName);
628: }
629:
630: $key = $propertyName;
631: if ($scope->isInClass()) {
632: $key = sprintf('%s-%s', $key, $scope->getClassReflection()->getCacheKey());
633: }
634:
635: if (!isset($this->properties[$key])) {
636: foreach ($this->propertiesClassReflectionExtensions as $i => $extension) {
637: if ($i > 0 && !$this->allowsDynamicPropertiesExtensions()) {
638: break;
639: }
640:
641: if (!$extension->hasProperty($this, $propertyName)) {
642: continue;
643: }
644:
645: $property = $this->wrapExtendedProperty($extension->getProperty($this, $propertyName));
646: if ($scope->canReadProperty($property)) {
647: return $this->properties[$key] = $property;
648: }
649: $this->properties[$key] = $property;
650: }
651: }
652:
653: if (!isset($this->properties[$key])) {
654: if ($this->requireExtendsPropertiesClassReflectionExtension->hasProperty($this, $propertyName)) {
655: $property = $this->requireExtendsPropertiesClassReflectionExtension->getProperty($this, $propertyName);
656: $this->properties[$key] = $property;
657: }
658: }
659:
660: if (!isset($this->properties[$key])) {
661: throw new MissingPropertyFromReflectionException($this->getName(), $propertyName);
662: }
663:
664: return $this->properties[$key];
665: }
666:
667: public function hasNativeProperty(string $propertyName): bool
668: {
669: return $this->getPhpExtension()->hasProperty($this, $propertyName);
670: }
671:
672: public function getNativeProperty(string $propertyName): PhpPropertyReflection
673: {
674: if (!$this->hasNativeProperty($propertyName)) {
675: throw new MissingPropertyFromReflectionException($this->getName(), $propertyName);
676: }
677:
678: return $this->getPhpExtension()->getNativeProperty($this, $propertyName);
679: }
680:
681: public function isAbstract(): bool
682: {
683: return $this->reflection->isAbstract();
684: }
685:
686: public function isInterface(): bool
687: {
688: return $this->reflection->isInterface();
689: }
690:
691: public function isTrait(): bool
692: {
693: return $this->reflection->isTrait();
694: }
695:
696: /**
697: * @phpstan-assert-if-true ReflectionEnum $this->reflection
698: */
699: public function isEnum(): bool
700: {
701: return $this->reflection instanceof ReflectionEnum && $this->reflection->isEnum();
702: }
703:
704: /**
705: * @return 'Interface'|'Trait'|'Enum'|'Class'
706: */
707: public function getClassTypeDescription(): string
708: {
709: if ($this->isInterface()) {
710: return 'Interface';
711: } elseif ($this->isTrait()) {
712: return 'Trait';
713: } elseif ($this->isEnum()) {
714: return 'Enum';
715: }
716:
717: return 'Class';
718: }
719:
720: public function isReadOnly(): bool
721: {
722: return $this->reflection->isReadOnly();
723: }
724:
725: public function isBackedEnum(): bool
726: {
727: if (!$this->reflection instanceof ReflectionEnum) {
728: return false;
729: }
730:
731: return $this->reflection->isBacked();
732: }
733:
734: public function getBackedEnumType(): ?Type
735: {
736: if (!$this->reflection instanceof ReflectionEnum) {
737: return null;
738: }
739:
740: if (!$this->reflection->isBacked()) {
741: return null;
742: }
743:
744: return TypehintHelper::decideTypeFromReflection($this->reflection->getBackingType());
745: }
746:
747: public function hasEnumCase(string $name): bool
748: {
749: if (!$this->isEnum()) {
750: return false;
751: }
752:
753: return $this->reflection->hasCase($name);
754: }
755:
756: /**
757: * @return array<string, EnumCaseReflection>
758: */
759: public function getEnumCases(): array
760: {
761: if (!$this->isEnum()) {
762: throw new ShouldNotHappenException();
763: }
764:
765: if ($this->enumCases !== null) {
766: return $this->enumCases;
767: }
768:
769: $cases = [];
770: $initializerExprContext = InitializerExprContext::fromClassReflection($this);
771: foreach ($this->reflection->getCases() as $case) {
772: $valueType = null;
773: if ($case instanceof ReflectionEnumBackedCase) {
774: $valueType = $this->initializerExprTypeResolver->getType($case->getValueExpression(), $initializerExprContext);
775: }
776: $caseName = $case->getName();
777: $cases[$caseName] = new EnumCaseReflection($this, $case, $valueType, $this->attributeReflectionFactory->fromNativeReflection($case->getAttributes(), InitializerExprContext::fromClass($this->getName(), $this->getFileName())));
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, $case, $valueType, $this->attributeReflectionFactory->fromNativeReflection($case->getAttributes(), InitializerExprContext::fromClass($this->getName(), $this->getFileName())));
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: $this->attributeReflectionFactory->fromNativeReflection($reflectionConstant->getAttributes(), InitializerExprContext::fromClass($declaringClass->getName(), $fileName)),
1097: );
1098: }
1099: return $this->constants[$name];
1100: }
1101:
1102: public function hasTraitUse(string $traitName): bool
1103: {
1104: return in_array($traitName, $this->getTraitNames(), true);
1105: }
1106:
1107: /**
1108: * @return list<string>
1109: */
1110: private function getTraitNames(): array
1111: {
1112: $class = $this->reflection;
1113: $traitNames = array_map(static fn (ReflectionClass $class) => $class->getName(), $this->collectTraits($class));
1114: while ($class->getParentClass() !== false) {
1115: $traitNames = array_values(array_unique(array_merge($traitNames, $class->getParentClass()->getTraitNames())));
1116: $class = $class->getParentClass();
1117: }
1118:
1119: return $traitNames;
1120: }
1121:
1122: /**
1123: * @return array<string, TypeAlias>
1124: */
1125: public function getTypeAliases(): array
1126: {
1127: if ($this->typeAliases === null) {
1128: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1129: if ($resolvedPhpDoc === null) {
1130: return $this->typeAliases = [];
1131: }
1132:
1133: $typeAliasImportTags = $resolvedPhpDoc->getTypeAliasImportTags();
1134: $typeAliasTags = $resolvedPhpDoc->getTypeAliasTags();
1135:
1136: // prevent circular imports
1137: if (array_key_exists($this->getName(), self::$resolvingTypeAliasImports)) {
1138: throw new CircularTypeAliasDefinitionException();
1139: }
1140:
1141: self::$resolvingTypeAliasImports[$this->getName()] = true;
1142:
1143: $importedAliases = array_map(function (TypeAliasImportTag $typeAliasImportTag): ?TypeAlias {
1144: $importedAlias = $typeAliasImportTag->getImportedAlias();
1145: $importedFromClassName = $typeAliasImportTag->getImportedFrom();
1146:
1147: if (!$this->reflectionProvider->hasClass($importedFromClassName)) {
1148: return null;
1149: }
1150:
1151: $importedFromReflection = $this->reflectionProvider->getClass($importedFromClassName);
1152:
1153: try {
1154: $typeAliases = $importedFromReflection->getTypeAliases();
1155: } catch (CircularTypeAliasDefinitionException) {
1156: return TypeAlias::invalid();
1157: }
1158:
1159: if (!array_key_exists($importedAlias, $typeAliases)) {
1160: return null;
1161: }
1162:
1163: return $typeAliases[$importedAlias];
1164: }, $typeAliasImportTags);
1165:
1166: unset(self::$resolvingTypeAliasImports[$this->getName()]);
1167:
1168: $localAliases = array_map(static fn (TypeAliasTag $typeAliasTag): TypeAlias => $typeAliasTag->getTypeAlias(), $typeAliasTags);
1169:
1170: $this->typeAliases = array_filter(
1171: array_merge($importedAliases, $localAliases),
1172: static fn (?TypeAlias $typeAlias): bool => $typeAlias !== null,
1173: );
1174: }
1175:
1176: return $this->typeAliases;
1177: }
1178:
1179: public function getDeprecatedDescription(): ?string
1180: {
1181: if ($this->deprecatedDescription === null && $this->isDeprecated()) {
1182: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1183: if ($resolvedPhpDoc !== null && $resolvedPhpDoc->getDeprecatedTag() !== null) {
1184: $this->deprecatedDescription = $resolvedPhpDoc->getDeprecatedTag()->getMessage();
1185: }
1186: }
1187:
1188: return $this->deprecatedDescription;
1189: }
1190:
1191: public function isDeprecated(): bool
1192: {
1193: if ($this->isDeprecated === null) {
1194: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1195: $this->isDeprecated = $resolvedPhpDoc !== null && $resolvedPhpDoc->isDeprecated();
1196: }
1197:
1198: return $this->isDeprecated;
1199: }
1200:
1201: public function isBuiltin(): bool
1202: {
1203: return $this->reflection->isInternal();
1204: }
1205:
1206: public function isInternal(): bool
1207: {
1208: if ($this->isInternal === null) {
1209: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1210: $this->isInternal = $resolvedPhpDoc !== null && $resolvedPhpDoc->isInternal();
1211: }
1212:
1213: return $this->isInternal;
1214: }
1215:
1216: public function isFinal(): bool
1217: {
1218: if ($this->isFinalByKeyword()) {
1219: return true;
1220: }
1221:
1222: if ($this->isFinal === null) {
1223: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1224: $this->isFinal = $resolvedPhpDoc !== null && $resolvedPhpDoc->isFinal();
1225: }
1226:
1227: return $this->isFinal;
1228: }
1229:
1230: public function isImmutable(): bool
1231: {
1232: if ($this->isImmutable === null) {
1233: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1234: $this->isImmutable = $resolvedPhpDoc !== null && ($resolvedPhpDoc->isImmutable() || $resolvedPhpDoc->isReadOnly());
1235:
1236: $parentClass = $this->getParentClass();
1237: if ($parentClass !== null && !$this->isImmutable) {
1238: $this->isImmutable = $parentClass->isImmutable();
1239: }
1240: }
1241:
1242: return $this->isImmutable;
1243: }
1244:
1245: public function hasConsistentConstructor(): bool
1246: {
1247: if ($this->hasConsistentConstructor === null) {
1248: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1249: $this->hasConsistentConstructor = $resolvedPhpDoc !== null && $resolvedPhpDoc->hasConsistentConstructor();
1250: }
1251:
1252: return $this->hasConsistentConstructor;
1253: }
1254:
1255: public function acceptsNamedArguments(): bool
1256: {
1257: if ($this->acceptsNamedArguments === null) {
1258: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1259: $this->acceptsNamedArguments = $resolvedPhpDoc === null || $resolvedPhpDoc->acceptsNamedArguments();
1260: }
1261:
1262: return $this->acceptsNamedArguments;
1263: }
1264:
1265: public function isFinalByKeyword(): bool
1266: {
1267: if ($this->isAnonymous()) {
1268: return true;
1269: }
1270:
1271: return $this->reflection->isFinal();
1272: }
1273:
1274: public function isAttributeClass(): bool
1275: {
1276: return $this->findAttributeFlags() !== null;
1277: }
1278:
1279: private function findAttributeFlags(): ?int
1280: {
1281: if ($this->isInterface() || $this->isTrait() || $this->isEnum()) {
1282: return null;
1283: }
1284:
1285: $nativeAttributes = $this->reflection->getAttributes(Attribute::class);
1286: if (count($nativeAttributes) === 1) {
1287: if (!$this->reflectionProvider->hasClass(Attribute::class)) {
1288: return null;
1289: }
1290:
1291: $attributeClass = $this->reflectionProvider->getClass(Attribute::class);
1292: $arguments = [];
1293: foreach ($nativeAttributes[0]->getArgumentsExpressions() as $i => $expression) {
1294: $arguments[] = new Arg($expression, false, false, [], is_int($i) ? null : new Identifier($i));
1295: }
1296:
1297: if (!$attributeClass->hasConstructor()) {
1298: return null;
1299: }
1300: $attributeConstructor = $attributeClass->getConstructor();
1301: $attributeConstructorVariant = $attributeConstructor->getOnlyVariant();
1302:
1303: if (count($arguments) === 0) {
1304: $flagType = $attributeConstructorVariant->getParameters()[0]->getDefaultValue();
1305: } else {
1306: $staticCall = ArgumentsNormalizer::reorderStaticCallArguments(
1307: $attributeConstructorVariant,
1308: new StaticCall(new FullyQualified(Attribute::class), $attributeConstructor->getName(), $arguments),
1309: );
1310: if ($staticCall === null) {
1311: return null;
1312: }
1313: $flagExpr = $staticCall->getArgs()[0]->value;
1314: $flagType = $this->initializerExprTypeResolver->getType($flagExpr, InitializerExprContext::fromClassReflection($this));
1315: }
1316:
1317: if (!$flagType instanceof ConstantIntegerType) {
1318: return null;
1319: }
1320:
1321: return $flagType->getValue();
1322: }
1323:
1324: return null;
1325: }
1326:
1327: /**
1328: * @return list<AttributeReflection>
1329: */
1330: public function getAttributes(): array
1331: {
1332: return $this->attributeReflectionFactory->fromNativeReflection($this->reflection->getAttributes(), InitializerExprContext::fromClass($this->getName(), $this->getFileName()));
1333: }
1334:
1335: public function getAttributeClassFlags(): int
1336: {
1337: $flags = $this->findAttributeFlags();
1338: if ($flags === null) {
1339: throw new ShouldNotHappenException();
1340: }
1341:
1342: return $flags;
1343: }
1344:
1345: public function getTemplateTypeMap(): TemplateTypeMap
1346: {
1347: if ($this->templateTypeMap !== null) {
1348: return $this->templateTypeMap;
1349: }
1350:
1351: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1352: if ($resolvedPhpDoc === null) {
1353: $this->templateTypeMap = TemplateTypeMap::createEmpty();
1354: return $this->templateTypeMap;
1355: }
1356:
1357: $templateTypeScope = TemplateTypeScope::createWithClass($this->getName());
1358:
1359: $templateTypeMap = new TemplateTypeMap(array_map(static fn (TemplateTag $tag): Type => TemplateTypeFactory::fromTemplateTag($templateTypeScope, $tag), $this->getTemplateTags()));
1360:
1361: $this->templateTypeMap = $templateTypeMap;
1362:
1363: return $templateTypeMap;
1364: }
1365:
1366: public function getActiveTemplateTypeMap(): TemplateTypeMap
1367: {
1368: if ($this->activeTemplateTypeMap !== null) {
1369: return $this->activeTemplateTypeMap;
1370: }
1371: $resolved = $this->resolvedTemplateTypeMap;
1372: if ($resolved !== null) {
1373: $templateTypeMap = $this->getTemplateTypeMap();
1374: return $this->activeTemplateTypeMap = $resolved->map(static function (string $name, Type $type) use ($templateTypeMap): Type {
1375: if ($type instanceof ErrorType) {
1376: $templateType = $templateTypeMap->getType($name);
1377: if ($templateType !== null) {
1378: return TemplateTypeHelper::resolveToDefaults($templateType);
1379: }
1380: }
1381:
1382: return $type;
1383: });
1384: }
1385:
1386: return $this->activeTemplateTypeMap = $this->getTemplateTypeMap();
1387: }
1388:
1389: public function getPossiblyIncompleteActiveTemplateTypeMap(): TemplateTypeMap
1390: {
1391: return $this->resolvedTemplateTypeMap ?? $this->getTemplateTypeMap();
1392: }
1393:
1394: private function getDefaultCallSiteVarianceMap(): TemplateTypeVarianceMap
1395: {
1396: if ($this->defaultCallSiteVarianceMap !== null) {
1397: return $this->defaultCallSiteVarianceMap;
1398: }
1399:
1400: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1401: if ($resolvedPhpDoc === null) {
1402: $this->defaultCallSiteVarianceMap = TemplateTypeVarianceMap::createEmpty();
1403: return $this->defaultCallSiteVarianceMap;
1404: }
1405:
1406: $map = [];
1407: foreach ($this->getTemplateTags() as $templateTag) {
1408: $map[$templateTag->getName()] = TemplateTypeVariance::createInvariant();
1409: }
1410:
1411: $this->defaultCallSiteVarianceMap = new TemplateTypeVarianceMap($map);
1412: return $this->defaultCallSiteVarianceMap;
1413: }
1414:
1415: public function getCallSiteVarianceMap(): TemplateTypeVarianceMap
1416: {
1417: return $this->callSiteVarianceMap ??= $this->resolvedCallSiteVarianceMap ?? $this->getDefaultCallSiteVarianceMap();
1418: }
1419:
1420: public function isGeneric(): bool
1421: {
1422: if ($this->isGeneric === null) {
1423: if ($this->isEnum()) {
1424: return $this->isGeneric = false;
1425: }
1426:
1427: $this->isGeneric = count($this->getTemplateTags()) > 0;
1428: }
1429:
1430: return $this->isGeneric;
1431: }
1432:
1433: /**
1434: * @param array<int, Type> $types
1435: */
1436: public function typeMapFromList(array $types): TemplateTypeMap
1437: {
1438: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1439: if ($resolvedPhpDoc === null) {
1440: return TemplateTypeMap::createEmpty();
1441: }
1442:
1443: $map = [];
1444: $i = 0;
1445: foreach ($resolvedPhpDoc->getTemplateTags() as $tag) {
1446: $map[$tag->getName()] = $types[$i] ?? $tag->getDefault() ?? $tag->getBound();
1447: $i++;
1448: }
1449:
1450: return new TemplateTypeMap($map);
1451: }
1452:
1453: /**
1454: * @param array<int, TemplateTypeVariance> $variances
1455: */
1456: public function varianceMapFromList(array $variances): TemplateTypeVarianceMap
1457: {
1458: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1459: if ($resolvedPhpDoc === null) {
1460: return new TemplateTypeVarianceMap([]);
1461: }
1462:
1463: $map = [];
1464: $i = 0;
1465: foreach ($resolvedPhpDoc->getTemplateTags() as $tag) {
1466: $map[$tag->getName()] = $variances[$i] ?? TemplateTypeVariance::createInvariant();
1467: $i++;
1468: }
1469:
1470: return new TemplateTypeVarianceMap($map);
1471: }
1472:
1473: /** @return list<Type> */
1474: public function typeMapToList(TemplateTypeMap $typeMap): array
1475: {
1476: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1477: if ($resolvedPhpDoc === null) {
1478: return [];
1479: }
1480:
1481: $list = [];
1482: foreach ($resolvedPhpDoc->getTemplateTags() as $tag) {
1483: $list[] = $typeMap->getType($tag->getName()) ?? $tag->getDefault() ?? $tag->getBound();
1484: }
1485:
1486: return $list;
1487: }
1488:
1489: /** @return list<TemplateTypeVariance> */
1490: public function varianceMapToList(TemplateTypeVarianceMap $varianceMap): array
1491: {
1492: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1493: if ($resolvedPhpDoc === null) {
1494: return [];
1495: }
1496:
1497: $list = [];
1498: foreach ($resolvedPhpDoc->getTemplateTags() as $tag) {
1499: $list[] = $varianceMap->getVariance($tag->getName()) ?? TemplateTypeVariance::createInvariant();
1500: }
1501:
1502: return $list;
1503: }
1504:
1505: /**
1506: * @param array<int, Type> $types
1507: */
1508: public function withTypes(array $types): self
1509: {
1510: return new self(
1511: $this->reflectionProvider,
1512: $this->initializerExprTypeResolver,
1513: $this->fileTypeMapper,
1514: $this->stubPhpDocProvider,
1515: $this->phpDocInheritanceResolver,
1516: $this->phpVersion,
1517: $this->signatureMapProvider,
1518: $this->attributeReflectionFactory,
1519: $this->propertiesClassReflectionExtensions,
1520: $this->methodsClassReflectionExtensions,
1521: $this->allowedSubTypesClassReflectionExtensions,
1522: $this->requireExtendsPropertiesClassReflectionExtension,
1523: $this->requireExtendsMethodsClassReflectionExtension,
1524: $this->displayName,
1525: $this->reflection,
1526: $this->anonymousFilename,
1527: $this->typeMapFromList($types),
1528: $this->stubPhpDocBlock,
1529: $this->universalObjectCratesClasses,
1530: null,
1531: $this->resolvedCallSiteVarianceMap,
1532: );
1533: }
1534:
1535: /**
1536: * @param array<int, TemplateTypeVariance> $variances
1537: */
1538: public function withVariances(array $variances): self
1539: {
1540: return new self(
1541: $this->reflectionProvider,
1542: $this->initializerExprTypeResolver,
1543: $this->fileTypeMapper,
1544: $this->stubPhpDocProvider,
1545: $this->phpDocInheritanceResolver,
1546: $this->phpVersion,
1547: $this->signatureMapProvider,
1548: $this->attributeReflectionFactory,
1549: $this->propertiesClassReflectionExtensions,
1550: $this->methodsClassReflectionExtensions,
1551: $this->allowedSubTypesClassReflectionExtensions,
1552: $this->requireExtendsPropertiesClassReflectionExtension,
1553: $this->requireExtendsMethodsClassReflectionExtension,
1554: $this->displayName,
1555: $this->reflection,
1556: $this->anonymousFilename,
1557: $this->resolvedTemplateTypeMap,
1558: $this->stubPhpDocBlock,
1559: $this->universalObjectCratesClasses,
1560: null,
1561: $this->varianceMapFromList($variances),
1562: );
1563: }
1564:
1565: public function getResolvedPhpDoc(): ?ResolvedPhpDocBlock
1566: {
1567: if ($this->stubPhpDocBlock !== null) {
1568: return $this->stubPhpDocBlock;
1569: }
1570:
1571: $fileName = $this->getFileName();
1572: if (is_bool($this->reflectionDocComment)) {
1573: $docComment = $this->reflection->getDocComment();
1574: $this->reflectionDocComment = $docComment !== false ? $docComment : null;
1575: }
1576:
1577: if ($this->reflectionDocComment === null) {
1578: return null;
1579: }
1580:
1581: if ($this->resolvedPhpDocBlock !== false) {
1582: return $this->resolvedPhpDocBlock;
1583: }
1584:
1585: return $this->resolvedPhpDocBlock = $this->fileTypeMapper->getResolvedPhpDoc($fileName, $this->getName(), null, null, $this->reflectionDocComment);
1586: }
1587:
1588: public function getTraitContextResolvedPhpDoc(self $implementingClass): ?ResolvedPhpDocBlock
1589: {
1590: if (!$this->isTrait()) {
1591: throw new ShouldNotHappenException();
1592: }
1593: if ($implementingClass->isTrait()) {
1594: throw new ShouldNotHappenException();
1595: }
1596: $fileName = $this->getFileName();
1597: if (is_bool($this->reflectionDocComment)) {
1598: $docComment = $this->reflection->getDocComment();
1599: $this->reflectionDocComment = $docComment !== false ? $docComment : null;
1600: }
1601:
1602: if ($this->reflectionDocComment === null) {
1603: return null;
1604: }
1605:
1606: if ($this->traitContextResolvedPhpDocBlock !== false) {
1607: return $this->traitContextResolvedPhpDocBlock;
1608: }
1609:
1610: return $this->traitContextResolvedPhpDocBlock = $this->fileTypeMapper->getResolvedPhpDoc($fileName, $implementingClass->getName(), $this->getName(), null, $this->reflectionDocComment);
1611: }
1612:
1613: private function getFirstExtendsTag(): ?ExtendsTag
1614: {
1615: foreach ($this->getExtendsTags() as $tag) {
1616: return $tag;
1617: }
1618:
1619: return null;
1620: }
1621:
1622: /** @return array<string, ExtendsTag> */
1623: public function getExtendsTags(): array
1624: {
1625: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1626: if ($resolvedPhpDoc === null) {
1627: return [];
1628: }
1629:
1630: return $resolvedPhpDoc->getExtendsTags();
1631: }
1632:
1633: /** @return array<string, ImplementsTag> */
1634: public function getImplementsTags(): array
1635: {
1636: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1637: if ($resolvedPhpDoc === null) {
1638: return [];
1639: }
1640:
1641: return $resolvedPhpDoc->getImplementsTags();
1642: }
1643:
1644: /** @return array<string,TemplateTag> */
1645: public function getTemplateTags(): array
1646: {
1647: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1648: if ($resolvedPhpDoc === null) {
1649: return [];
1650: }
1651:
1652: return $resolvedPhpDoc->getTemplateTags();
1653: }
1654:
1655: /**
1656: * @return array<string,ClassReflection>
1657: */
1658: public function getAncestors(): array
1659: {
1660: $ancestors = $this->ancestors;
1661:
1662: if ($ancestors === null) {
1663: $ancestors = [
1664: $this->getName() => $this,
1665: ];
1666:
1667: $addToAncestors = static function (string $name, ClassReflection $classReflection) use (&$ancestors): void {
1668: if (array_key_exists($name, $ancestors)) {
1669: return;
1670: }
1671:
1672: $ancestors[$name] = $classReflection;
1673: };
1674:
1675: foreach ($this->getInterfaces() as $interface) {
1676: $addToAncestors($interface->getName(), $interface);
1677: foreach ($interface->getAncestors() as $name => $ancestor) {
1678: $addToAncestors($name, $ancestor);
1679: }
1680: }
1681:
1682: foreach ($this->getTraits() as $trait) {
1683: $addToAncestors($trait->getName(), $trait);
1684: foreach ($trait->getAncestors() as $name => $ancestor) {
1685: $addToAncestors($name, $ancestor);
1686: }
1687: }
1688:
1689: $parent = $this->getParentClass();
1690: if ($parent !== null) {
1691: $addToAncestors($parent->getName(), $parent);
1692: foreach ($parent->getAncestors() as $name => $ancestor) {
1693: $addToAncestors($name, $ancestor);
1694: }
1695: }
1696:
1697: $this->ancestors = $ancestors;
1698: }
1699:
1700: return $ancestors;
1701: }
1702:
1703: public function getAncestorWithClassName(string $className): ?self
1704: {
1705: return $this->getAncestors()[$className] ?? null;
1706: }
1707:
1708: /**
1709: * @param string[] $ancestorClasses
1710: */
1711: private function isValidAncestorType(Type $type, array $ancestorClasses): bool
1712: {
1713: if (!$type instanceof GenericObjectType) {
1714: return false;
1715: }
1716:
1717: $reflection = $type->getClassReflection();
1718: if ($reflection === null) {
1719: return false;
1720: }
1721:
1722: return in_array($reflection->getName(), $ancestorClasses, true);
1723: }
1724:
1725: /**
1726: * @return array<MixinTag>
1727: */
1728: public function getMixinTags(): array
1729: {
1730: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1731: if ($resolvedPhpDoc === null) {
1732: return [];
1733: }
1734:
1735: return $resolvedPhpDoc->getMixinTags();
1736: }
1737:
1738: /**
1739: * @return array<RequireExtendsTag>
1740: */
1741: public function getRequireExtendsTags(): array
1742: {
1743: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1744: if ($resolvedPhpDoc === null) {
1745: return [];
1746: }
1747:
1748: return $resolvedPhpDoc->getRequireExtendsTags();
1749: }
1750:
1751: /**
1752: * @return array<RequireImplementsTag>
1753: */
1754: public function getRequireImplementsTags(): array
1755: {
1756: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1757: if ($resolvedPhpDoc === null) {
1758: return [];
1759: }
1760:
1761: return $resolvedPhpDoc->getRequireImplementsTags();
1762: }
1763:
1764: /**
1765: * @return array<string, PropertyTag>
1766: */
1767: public function getPropertyTags(): array
1768: {
1769: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1770: if ($resolvedPhpDoc === null) {
1771: return [];
1772: }
1773:
1774: return $resolvedPhpDoc->getPropertyTags();
1775: }
1776:
1777: /**
1778: * @return array<string, MethodTag>
1779: */
1780: public function getMethodTags(): array
1781: {
1782: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1783: if ($resolvedPhpDoc === null) {
1784: return [];
1785: }
1786:
1787: return $resolvedPhpDoc->getMethodTags();
1788: }
1789:
1790: /**
1791: * @return list<Type>
1792: */
1793: public function getResolvedMixinTypes(): array
1794: {
1795: $types = [];
1796: foreach ($this->getMixinTags() as $mixinTag) {
1797: if (!$this->isGeneric()) {
1798: $types[] = $mixinTag->getType();
1799: continue;
1800: }
1801:
1802: $types[] = TemplateTypeHelper::resolveTemplateTypes(
1803: $mixinTag->getType(),
1804: $this->getActiveTemplateTypeMap(),
1805: $this->getCallSiteVarianceMap(),
1806: TemplateTypeVariance::createStatic(),
1807: );
1808: }
1809:
1810: return $types;
1811: }
1812:
1813: /**
1814: * @return array<Type>|null
1815: */
1816: public function getAllowedSubTypes(): ?array
1817: {
1818: foreach ($this->allowedSubTypesClassReflectionExtensions as $allowedSubTypesClassReflectionExtension) {
1819: if ($allowedSubTypesClassReflectionExtension->supports($this)) {
1820: return $allowedSubTypesClassReflectionExtension->getAllowedSubTypes($this);
1821: }
1822: }
1823:
1824: return null;
1825: }
1826:
1827: }
1828: