1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Type;
4:
5: use ArrayAccess;
6: use Closure;
7: use Countable;
8: use DateTime;
9: use DateTimeImmutable;
10: use DateTimeInterface;
11: use Error;
12: use Exception;
13: use Iterator;
14: use IteratorAggregate;
15: use PHPStan\Analyser\OutOfClassScope;
16: use PHPStan\Broker\Broker;
17: use PHPStan\Broker\ClassNotFoundException;
18: use PHPStan\Php\PhpVersion;
19: use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
20: use PHPStan\PhpDocParser\Ast\Type\TypeNode;
21: use PHPStan\Reflection\Callables\CallableParametersAcceptor;
22: use PHPStan\Reflection\Callables\FunctionCallableVariant;
23: use PHPStan\Reflection\ClassMemberAccessAnswerer;
24: use PHPStan\Reflection\ClassReflection;
25: use PHPStan\Reflection\ConstantReflection;
26: use PHPStan\Reflection\ExtendedMethodReflection;
27: use PHPStan\Reflection\ParametersAcceptorSelector;
28: use PHPStan\Reflection\Php\UniversalObjectCratesClassReflectionExtension;
29: use PHPStan\Reflection\PropertyReflection;
30: use PHPStan\Reflection\ReflectionProviderStaticAccessor;
31: use PHPStan\Reflection\TrivialParametersAcceptor;
32: use PHPStan\Reflection\Type\CalledOnTypeUnresolvedMethodPrototypeReflection;
33: use PHPStan\Reflection\Type\CalledOnTypeUnresolvedPropertyPrototypeReflection;
34: use PHPStan\Reflection\Type\UnionTypeUnresolvedPropertyPrototypeReflection;
35: use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection;
36: use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection;
37: use PHPStan\ShouldNotHappenException;
38: use PHPStan\TrinaryLogic;
39: use PHPStan\Type\Constant\ConstantArrayType;
40: use PHPStan\Type\Constant\ConstantBooleanType;
41: use PHPStan\Type\Constant\ConstantStringType;
42: use PHPStan\Type\Enum\EnumCaseObjectType;
43: use PHPStan\Type\Generic\GenericObjectType;
44: use PHPStan\Type\Generic\TemplateTypeHelper;
45: use PHPStan\Type\Traits\MaybeIterableTypeTrait;
46: use PHPStan\Type\Traits\NonArrayTypeTrait;
47: use PHPStan\Type\Traits\NonGeneralizableTypeTrait;
48: use PHPStan\Type\Traits\NonGenericTypeTrait;
49: use PHPStan\Type\Traits\UndecidedComparisonTypeTrait;
50: use Throwable;
51: use Traversable;
52: use function array_key_exists;
53: use function array_map;
54: use function array_values;
55: use function count;
56: use function implode;
57: use function in_array;
58: use function sprintf;
59: use function strtolower;
60:
61: /** @api */
62: class ObjectType implements TypeWithClassName, SubtractableType
63: {
64:
65: use MaybeIterableTypeTrait;
66: use NonArrayTypeTrait;
67: use NonGenericTypeTrait;
68: use UndecidedComparisonTypeTrait;
69: use NonGeneralizableTypeTrait;
70:
71: private const EXTRA_OFFSET_CLASSES = ['SimpleXMLElement', 'DOMNodeList', 'Threaded'];
72:
73: private ?Type $subtractedType;
74:
75: /** @var array<string, array<string, TrinaryLogic>> */
76: private static array $superTypes = [];
77:
78: private ?self $cachedParent = null;
79:
80: /** @var self[]|null */
81: private ?array $cachedInterfaces = null;
82:
83: /** @var array<string, array<string, array<string, UnresolvedMethodPrototypeReflection>>> */
84: private static array $methods = [];
85:
86: /** @var array<string, array<string, array<string, UnresolvedPropertyPrototypeReflection>>> */
87: private static array $properties = [];
88:
89: /** @var array<string, array<string, self|null>> */
90: private static array $ancestors = [];
91:
92: /** @var array<string, self|null> */
93: private array $currentAncestors = [];
94:
95: private ?string $cachedDescription = null;
96:
97: /** @api */
98: public function __construct(
99: private string $className,
100: ?Type $subtractedType = null,
101: private ?ClassReflection $classReflection = null,
102: )
103: {
104: if ($subtractedType instanceof NeverType) {
105: $subtractedType = null;
106: }
107:
108: $this->subtractedType = $subtractedType;
109: }
110:
111: public static function resetCaches(): void
112: {
113: self::$superTypes = [];
114: self::$methods = [];
115: self::$properties = [];
116: self::$ancestors = [];
117: }
118:
119: private static function createFromReflection(ClassReflection $reflection): self
120: {
121: if (!$reflection->isGeneric()) {
122: return new ObjectType($reflection->getName());
123: }
124:
125: return new GenericObjectType(
126: $reflection->getName(),
127: $reflection->typeMapToList($reflection->getActiveTemplateTypeMap()),
128: null,
129: null,
130: $reflection->varianceMapToList($reflection->getCallSiteVarianceMap()),
131: );
132: }
133:
134: public function getClassName(): string
135: {
136: return $this->className;
137: }
138:
139: public function hasProperty(string $propertyName): TrinaryLogic
140: {
141: $classReflection = $this->getClassReflection();
142: if ($classReflection === null) {
143: return TrinaryLogic::createMaybe();
144: }
145:
146: if ($classReflection->hasProperty($propertyName)) {
147: return TrinaryLogic::createYes();
148: }
149:
150: if ($classReflection->allowsDynamicProperties()) {
151: return TrinaryLogic::createMaybe();
152: }
153:
154: if (!$classReflection->isFinal()) {
155: return TrinaryLogic::createMaybe();
156: }
157:
158: return TrinaryLogic::createNo();
159: }
160:
161: public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection
162: {
163: return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty();
164: }
165:
166: public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
167: {
168: if (!$scope->isInClass()) {
169: $canAccessProperty = 'no';
170: } else {
171: $canAccessProperty = $scope->getClassReflection()->getName();
172: }
173: $description = $this->describeCache();
174:
175: if (isset(self::$properties[$description][$propertyName][$canAccessProperty])) {
176: return self::$properties[$description][$propertyName][$canAccessProperty];
177: }
178:
179: $nakedClassReflection = $this->getNakedClassReflection();
180: if ($nakedClassReflection === null) {
181: throw new ClassNotFoundException($this->className);
182: }
183:
184: if ($nakedClassReflection->isEnum()) {
185: if (
186: $propertyName === 'name'
187: || ($propertyName === 'value' && $nakedClassReflection->isBackedEnum())
188: ) {
189: $properties = [];
190: foreach ($this->getEnumCases() as $enumCase) {
191: $properties[] = $enumCase->getUnresolvedPropertyPrototype($propertyName, $scope);
192: }
193:
194: if (count($properties) > 0) {
195: if (count($properties) === 1) {
196: return $properties[0];
197: }
198:
199: return new UnionTypeUnresolvedPropertyPrototypeReflection($propertyName, $properties);
200: }
201: }
202: }
203:
204: if (!$nakedClassReflection->hasNativeProperty($propertyName)) {
205: $nakedClassReflection = $this->getClassReflection();
206: }
207:
208: if ($nakedClassReflection === null) {
209: throw new ClassNotFoundException($this->className);
210: }
211:
212: $property = $nakedClassReflection->getProperty($propertyName, $scope);
213:
214: $ancestor = $this->getAncestorWithClassName($property->getDeclaringClass()->getName());
215: $resolvedClassReflection = null;
216: if ($ancestor !== null && $ancestor->hasProperty($propertyName)->yes()) {
217: $resolvedClassReflection = $ancestor->getClassReflection();
218: if ($ancestor !== $this) {
219: $property = $ancestor->getUnresolvedPropertyPrototype($propertyName, $scope)->getNakedProperty();
220: }
221: }
222: if ($resolvedClassReflection === null) {
223: $resolvedClassReflection = $property->getDeclaringClass();
224: }
225:
226: return self::$properties[$description][$propertyName][$canAccessProperty] = new CalledOnTypeUnresolvedPropertyPrototypeReflection(
227: $property,
228: $resolvedClassReflection,
229: true,
230: $this,
231: );
232: }
233:
234: public function getPropertyWithoutTransformingStatic(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection
235: {
236: $classReflection = $this->getNakedClassReflection();
237: if ($classReflection === null) {
238: throw new ClassNotFoundException($this->className);
239: }
240:
241: if (!$classReflection->hasProperty($propertyName)) {
242: $classReflection = $this->getClassReflection();
243: }
244:
245: if ($classReflection === null) {
246: throw new ClassNotFoundException($this->className);
247: }
248:
249: return $classReflection->getProperty($propertyName, $scope);
250: }
251:
252: /**
253: * @return string[]
254: */
255: public function getReferencedClasses(): array
256: {
257: return [$this->className];
258: }
259:
260: public function getObjectClassNames(): array
261: {
262: return [$this->className];
263: }
264:
265: public function getObjectClassReflections(): array
266: {
267: $classReflection = $this->getClassReflection();
268: if ($classReflection === null) {
269: return [];
270: }
271:
272: return [$classReflection];
273: }
274:
275: public function accepts(Type $type, bool $strictTypes): TrinaryLogic
276: {
277: return $this->acceptsWithReason($type, $strictTypes)->result;
278: }
279:
280: public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult
281: {
282: if ($type instanceof StaticType) {
283: return $this->checkSubclassAcceptability($type->getClassName());
284: }
285:
286: if ($type instanceof CompoundType) {
287: return $type->isAcceptedWithReasonBy($this, $strictTypes);
288: }
289:
290: if ($type instanceof ClosureType) {
291: return new AcceptsResult($this->isInstanceOf(Closure::class), []);
292: }
293:
294: if ($type instanceof ObjectWithoutClassType) {
295: return AcceptsResult::createMaybe();
296: }
297:
298: $thatClassNames = $type->getObjectClassNames();
299: if (count($thatClassNames) > 1) {
300: throw new ShouldNotHappenException();
301: }
302:
303: if ($thatClassNames === []) {
304: return AcceptsResult::createNo();
305: }
306:
307: return $this->checkSubclassAcceptability($thatClassNames[0]);
308: }
309:
310: public function isSuperTypeOf(Type $type): TrinaryLogic
311: {
312: $thatClassNames = $type->getObjectClassNames();
313: if (!$type instanceof CompoundType && $thatClassNames === [] && !$type instanceof ObjectWithoutClassType) {
314: return TrinaryLogic::createNo();
315: }
316:
317: $thisDescription = $this->describeCache();
318:
319: if ($type instanceof self) {
320: $description = $type->describeCache();
321: } else {
322: $description = $type->describe(VerbosityLevel::cache());
323: }
324:
325: if (isset(self::$superTypes[$thisDescription][$description])) {
326: return self::$superTypes[$thisDescription][$description];
327: }
328:
329: if ($type instanceof CompoundType) {
330: return self::$superTypes[$thisDescription][$description] = $type->isSubTypeOf($this);
331: }
332:
333: if ($type instanceof ClosureType) {
334: return self::$superTypes[$thisDescription][$description] = $this->isInstanceOf(Closure::class);
335: }
336:
337: if ($type instanceof ObjectWithoutClassType) {
338: if ($type->getSubtractedType() !== null) {
339: $isSuperType = $type->getSubtractedType()->isSuperTypeOf($this);
340: if ($isSuperType->yes()) {
341: return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createNo();
342: }
343: }
344: return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createMaybe();
345: }
346:
347: $transformResult = static fn (TrinaryLogic $result) => $result;
348: if ($this->subtractedType !== null) {
349: $isSuperType = $this->subtractedType->isSuperTypeOf($type);
350: if ($isSuperType->yes()) {
351: return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createNo();
352: }
353: if ($isSuperType->maybe()) {
354: $transformResult = static fn (TrinaryLogic $result) => $result->and(TrinaryLogic::createMaybe());
355: }
356: }
357:
358: if (
359: $type instanceof SubtractableType
360: && $type->getSubtractedType() !== null
361: ) {
362: $isSuperType = $type->getSubtractedType()->isSuperTypeOf($this);
363: if ($isSuperType->yes()) {
364: return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createNo();
365: }
366: }
367:
368: $thisClassName = $this->className;
369: if (count($thatClassNames) > 1) {
370: throw new ShouldNotHappenException();
371: }
372:
373: if ($thatClassNames[0] === $thisClassName) {
374: return $transformResult(TrinaryLogic::createYes());
375: }
376:
377: $reflectionProvider = ReflectionProviderStaticAccessor::getInstance();
378: $thisClassReflection = $this->getClassReflection();
379:
380: if ($thisClassReflection === null || !$reflectionProvider->hasClass($thatClassNames[0])) {
381: return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createMaybe();
382: }
383:
384: $thatClassReflection = $reflectionProvider->getClass($thatClassNames[0]);
385:
386: if ($thisClassReflection->isTrait() || $thatClassReflection->isTrait()) {
387: return TrinaryLogic::createNo();
388: }
389:
390: if ($thisClassReflection->getName() === $thatClassReflection->getName()) {
391: return self::$superTypes[$thisDescription][$description] = $transformResult(TrinaryLogic::createYes());
392: }
393:
394: if ($thatClassReflection->isSubclassOf($thisClassName)) {
395: return self::$superTypes[$thisDescription][$description] = $transformResult(TrinaryLogic::createYes());
396: }
397:
398: if ($thisClassReflection->isSubclassOf($thatClassNames[0])) {
399: return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createMaybe();
400: }
401:
402: if ($thisClassReflection->isInterface() && !$thatClassReflection->getNativeReflection()->isFinal()) {
403: return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createMaybe();
404: }
405:
406: if ($thatClassReflection->isInterface() && !$thisClassReflection->getNativeReflection()->isFinal()) {
407: return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createMaybe();
408: }
409:
410: return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createNo();
411: }
412:
413: public function equals(Type $type): bool
414: {
415: if (!$type instanceof self) {
416: return false;
417: }
418:
419: if ($type instanceof EnumCaseObjectType) {
420: return false;
421: }
422:
423: if ($this->className !== $type->className) {
424: return false;
425: }
426:
427: if ($this->subtractedType === null) {
428: return $type->subtractedType === null;
429: }
430:
431: if ($type->subtractedType === null) {
432: return false;
433: }
434:
435: return $this->subtractedType->equals($type->subtractedType);
436: }
437:
438: private function checkSubclassAcceptability(string $thatClass): AcceptsResult
439: {
440: if ($this->className === $thatClass) {
441: return AcceptsResult::createYes();
442: }
443:
444: $reflectionProvider = ReflectionProviderStaticAccessor::getInstance();
445:
446: if ($this->getClassReflection() === null || !$reflectionProvider->hasClass($thatClass)) {
447: return AcceptsResult::createNo();
448: }
449:
450: $thisReflection = $this->getClassReflection();
451: $thatReflection = $reflectionProvider->getClass($thatClass);
452:
453: if ($thisReflection->getName() === $thatReflection->getName()) {
454: // class alias
455: return AcceptsResult::createYes();
456: }
457:
458: if ($thisReflection->isInterface() && $thatReflection->isInterface()) {
459: return AcceptsResult::createFromBoolean(
460: $thatReflection->implementsInterface($thisReflection->getName()),
461: );
462: }
463:
464: return AcceptsResult::createFromBoolean(
465: $thatReflection->isSubclassOf($thisReflection->getName()),
466: );
467: }
468:
469: public function describe(VerbosityLevel $level): string
470: {
471: $preciseNameCallback = function (): string {
472: $reflectionProvider = ReflectionProviderStaticAccessor::getInstance();
473: if (!$reflectionProvider->hasClass($this->className)) {
474: return $this->className;
475: }
476:
477: return $reflectionProvider->getClassName($this->className);
478: };
479:
480: $preciseWithSubtracted = function () use ($level): string {
481: $description = $this->className;
482: if ($this->subtractedType !== null) {
483: $description .= sprintf('~%s', $this->subtractedType->describe($level));
484: }
485:
486: return $description;
487: };
488:
489: return $level->handle(
490: $preciseNameCallback,
491: $preciseNameCallback,
492: $preciseWithSubtracted,
493: function () use ($preciseWithSubtracted): string {
494: $reflection = $this->classReflection;
495: $line = '';
496: if ($reflection !== null) {
497: $line .= '-';
498: $line .= (string) $reflection->getNativeReflection()->getStartLine();
499: $line .= '-';
500: }
501:
502: return $preciseWithSubtracted() . '-' . static::class . '-' . $line . $this->describeAdditionalCacheKey();
503: },
504: );
505: }
506:
507: protected function describeAdditionalCacheKey(): string
508: {
509: return '';
510: }
511:
512: private function describeCache(): string
513: {
514: if ($this->cachedDescription !== null) {
515: return $this->cachedDescription;
516: }
517:
518: if (static::class !== self::class) {
519: return $this->cachedDescription = $this->describe(VerbosityLevel::cache());
520: }
521:
522: $description = $this->className;
523:
524: if ($this instanceof GenericObjectType) {
525: $description .= '<';
526: $typeDescriptions = [];
527: foreach ($this->getTypes() as $type) {
528: $typeDescriptions[] = $type->describe(VerbosityLevel::cache());
529: }
530: $description .= '<' . implode(', ', $typeDescriptions) . '>';
531: }
532:
533: if ($this->subtractedType !== null) {
534: $description .= sprintf('~%s', $this->subtractedType->describe(VerbosityLevel::cache()));
535: }
536:
537: $reflection = $this->classReflection;
538: if ($reflection !== null) {
539: $description .= '-';
540: $description .= (string) $reflection->getNativeReflection()->getStartLine();
541: $description .= '-';
542: }
543:
544: return $this->cachedDescription = $description;
545: }
546:
547: public function toNumber(): Type
548: {
549: if ($this->isInstanceOf('SimpleXMLElement')->yes()) {
550: return new UnionType([
551: new FloatType(),
552: new IntegerType(),
553: ]);
554: }
555:
556: return new ErrorType();
557: }
558:
559: public function toInteger(): Type
560: {
561: if ($this->isInstanceOf('SimpleXMLElement')->yes()) {
562: return new IntegerType();
563: }
564:
565: if (in_array($this->getClassName(), ['CurlHandle', 'CurlMultiHandle'], true)) {
566: return new IntegerType();
567: }
568:
569: return new ErrorType();
570: }
571:
572: public function toFloat(): Type
573: {
574: if ($this->isInstanceOf('SimpleXMLElement')->yes()) {
575: return new FloatType();
576: }
577: return new ErrorType();
578: }
579:
580: public function toString(): Type
581: {
582: $classReflection = $this->getClassReflection();
583: if ($classReflection === null) {
584: return new ErrorType();
585: }
586:
587: if ($classReflection->hasNativeMethod('__toString')) {
588: return ParametersAcceptorSelector::selectSingle($this->getMethod('__toString', new OutOfClassScope())->getVariants())->getReturnType();
589: }
590:
591: return new ErrorType();
592: }
593:
594: public function toArray(): Type
595: {
596: $classReflection = $this->getClassReflection();
597: if ($classReflection === null) {
598: return new ArrayType(new MixedType(), new MixedType());
599: }
600:
601: $reflectionProvider = ReflectionProviderStaticAccessor::getInstance();
602:
603: if (
604: !$classReflection->getNativeReflection()->isUserDefined()
605: || UniversalObjectCratesClassReflectionExtension::isUniversalObjectCrate(
606: $reflectionProvider,
607: Broker::getInstance()->getUniversalObjectCratesClasses(),
608: $classReflection,
609: )
610: ) {
611: return new ArrayType(new MixedType(), new MixedType());
612: }
613: $arrayKeys = [];
614: $arrayValues = [];
615:
616: $isFinal = $classReflection->isFinal();
617:
618: do {
619: foreach ($classReflection->getNativeReflection()->getProperties() as $nativeProperty) {
620: if ($nativeProperty->isStatic()) {
621: continue;
622: }
623:
624: $declaringClass = $reflectionProvider->getClass($nativeProperty->getDeclaringClass()->getName());
625: $property = $declaringClass->getNativeProperty($nativeProperty->getName());
626:
627: $keyName = $nativeProperty->getName();
628: if ($nativeProperty->isPrivate()) {
629: $keyName = sprintf(
630: "\0%s\0%s",
631: $declaringClass->getName(),
632: $keyName,
633: );
634: } elseif ($nativeProperty->isProtected()) {
635: $keyName = sprintf(
636: "\0*\0%s",
637: $keyName,
638: );
639: }
640:
641: $arrayKeys[] = new ConstantStringType($keyName);
642: $arrayValues[] = $property->getReadableType();
643: }
644:
645: $classReflection = $classReflection->getParentClass();
646: } while ($classReflection !== null);
647:
648: if (!$isFinal && count($arrayKeys) === 0) {
649: return new ArrayType(new MixedType(), new MixedType());
650: }
651:
652: return new ConstantArrayType($arrayKeys, $arrayValues);
653: }
654:
655: public function toArrayKey(): Type
656: {
657: return $this->toString();
658: }
659:
660: public function toBoolean(): BooleanType
661: {
662: if ($this->isInstanceOf('SimpleXMLElement')->yes()) {
663: return new BooleanType();
664: }
665:
666: return new ConstantBooleanType(true);
667: }
668:
669: public function isObject(): TrinaryLogic
670: {
671: return TrinaryLogic::createYes();
672: }
673:
674: public function isEnum(): TrinaryLogic
675: {
676: $classReflection = $this->getClassReflection();
677: if ($classReflection === null) {
678: return TrinaryLogic::createMaybe();
679: }
680:
681: return TrinaryLogic::createFromBoolean($classReflection->isEnum());
682: }
683:
684: public function canAccessProperties(): TrinaryLogic
685: {
686: return TrinaryLogic::createYes();
687: }
688:
689: public function canCallMethods(): TrinaryLogic
690: {
691: if (strtolower($this->className) === 'stdclass') {
692: return TrinaryLogic::createNo();
693: }
694:
695: return TrinaryLogic::createYes();
696: }
697:
698: public function hasMethod(string $methodName): TrinaryLogic
699: {
700: $classReflection = $this->getClassReflection();
701: if ($classReflection === null) {
702: return TrinaryLogic::createMaybe();
703: }
704:
705: if ($classReflection->hasMethod($methodName)) {
706: return TrinaryLogic::createYes();
707: }
708:
709: if ($classReflection->isFinal()) {
710: return TrinaryLogic::createNo();
711: }
712:
713: return TrinaryLogic::createMaybe();
714: }
715:
716: public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): ExtendedMethodReflection
717: {
718: return $this->getUnresolvedMethodPrototype($methodName, $scope)->getTransformedMethod();
719: }
720:
721: public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection
722: {
723: if (!$scope->isInClass()) {
724: $canCallMethod = 'no';
725: } else {
726: $canCallMethod = $scope->getClassReflection()->getName();
727: }
728: $description = $this->describeCache();
729: if (isset(self::$methods[$description][$methodName][$canCallMethod])) {
730: return self::$methods[$description][$methodName][$canCallMethod];
731: }
732:
733: $nakedClassReflection = $this->getNakedClassReflection();
734: if ($nakedClassReflection === null) {
735: throw new ClassNotFoundException($this->className);
736: }
737:
738: if (!$nakedClassReflection->hasNativeMethod($methodName)) {
739: $nakedClassReflection = $this->getClassReflection();
740: }
741:
742: if ($nakedClassReflection === null) {
743: throw new ClassNotFoundException($this->className);
744: }
745:
746: $method = $nakedClassReflection->getMethod($methodName, $scope);
747:
748: $ancestor = $this->getAncestorWithClassName($method->getDeclaringClass()->getName());
749: $resolvedClassReflection = null;
750: if ($ancestor !== null) {
751: $resolvedClassReflection = $ancestor->getClassReflection();
752: if ($ancestor !== $this) {
753: $method = $ancestor->getUnresolvedMethodPrototype($methodName, $scope)->getNakedMethod();
754: }
755: }
756: if ($resolvedClassReflection === null) {
757: $resolvedClassReflection = $method->getDeclaringClass();
758: }
759:
760: return self::$methods[$description][$methodName][$canCallMethod] = new CalledOnTypeUnresolvedMethodPrototypeReflection(
761: $method,
762: $resolvedClassReflection,
763: true,
764: $this,
765: );
766: }
767:
768: public function canAccessConstants(): TrinaryLogic
769: {
770: return TrinaryLogic::createYes();
771: }
772:
773: public function hasConstant(string $constantName): TrinaryLogic
774: {
775: $class = $this->getClassReflection();
776: if ($class === null) {
777: return TrinaryLogic::createNo();
778: }
779:
780: return TrinaryLogic::createFromBoolean(
781: $class->hasConstant($constantName),
782: );
783: }
784:
785: public function getConstant(string $constantName): ConstantReflection
786: {
787: $class = $this->getClassReflection();
788: if ($class === null) {
789: throw new ClassNotFoundException($this->className);
790: }
791:
792: return $class->getConstant($constantName);
793: }
794:
795: public function getTemplateType(string $ancestorClassName, string $templateTypeName): Type
796: {
797: $classReflection = $this->getClassReflection();
798: if ($classReflection === null) {
799: return new ErrorType();
800: }
801:
802: $ancestorClassReflection = $classReflection->getAncestorWithClassName($ancestorClassName);
803: if ($ancestorClassReflection === null) {
804: return new ErrorType();
805: }
806:
807: $activeTemplateTypeMap = $ancestorClassReflection->getPossiblyIncompleteActiveTemplateTypeMap();
808: $type = $activeTemplateTypeMap->getType($templateTypeName);
809: if ($type === null) {
810: return new ErrorType();
811: }
812: if ($type instanceof ErrorType) {
813: $templateTypeMap = $ancestorClassReflection->getTemplateTypeMap();
814: $templateType = $templateTypeMap->getType($templateTypeName);
815: if ($templateType === null) {
816: return $type;
817: }
818:
819: $bound = TemplateTypeHelper::resolveToBounds($templateType);
820: if ($bound instanceof MixedType && $bound->isExplicitMixed()) {
821: return new MixedType(false);
822: }
823:
824: return $bound;
825: }
826:
827: return $type;
828: }
829:
830: public function getConstantStrings(): array
831: {
832: return [];
833: }
834:
835: public function isIterable(): TrinaryLogic
836: {
837: return $this->isInstanceOf(Traversable::class);
838: }
839:
840: public function isIterableAtLeastOnce(): TrinaryLogic
841: {
842: return $this->isInstanceOf(Traversable::class)
843: ->and(TrinaryLogic::createMaybe());
844: }
845:
846: public function getArraySize(): Type
847: {
848: if ($this->isInstanceOf(Countable::class)->no()) {
849: return new ErrorType();
850: }
851:
852: return IntegerRangeType::fromInterval(0, null);
853: }
854:
855: public function getIterableKeyType(): Type
856: {
857: $isTraversable = false;
858: if ($this->isInstanceOf(IteratorAggregate::class)->yes()) {
859: $keyType = RecursionGuard::run($this, fn (): Type => ParametersAcceptorSelector::selectSingle(
860: $this->getMethod('getIterator', new OutOfClassScope())->getVariants(),
861: )->getReturnType()->getIterableKeyType());
862: $isTraversable = true;
863: if (!$keyType instanceof MixedType || $keyType->isExplicitMixed()) {
864: return $keyType;
865: }
866: }
867:
868: $extraOffsetAccessible = $this->isExtraOffsetAccessibleClass()->yes();
869: if (!$extraOffsetAccessible && $this->isInstanceOf(Traversable::class)->yes()) {
870: $isTraversable = true;
871: $tKey = $this->getTemplateType(Traversable::class, 'TKey');
872: if (!$tKey instanceof ErrorType) {
873: if (!$tKey instanceof MixedType || $tKey->isExplicitMixed()) {
874: return $tKey;
875: }
876: }
877: }
878:
879: if ($this->isInstanceOf(Iterator::class)->yes()) {
880: return RecursionGuard::run($this, fn (): Type => ParametersAcceptorSelector::selectSingle(
881: $this->getMethod('key', new OutOfClassScope())->getVariants(),
882: )->getReturnType());
883: }
884:
885: if ($extraOffsetAccessible) {
886: return new MixedType(true);
887: }
888:
889: if ($isTraversable) {
890: return new MixedType();
891: }
892:
893: return new ErrorType();
894: }
895:
896: public function getFirstIterableKeyType(): Type
897: {
898: return $this->getIterableKeyType();
899: }
900:
901: public function getLastIterableKeyType(): Type
902: {
903: return $this->getIterableKeyType();
904: }
905:
906: public function getIterableValueType(): Type
907: {
908: $isTraversable = false;
909: if ($this->isInstanceOf(IteratorAggregate::class)->yes()) {
910: $valueType = RecursionGuard::run($this, fn (): Type => ParametersAcceptorSelector::selectSingle(
911: $this->getMethod('getIterator', new OutOfClassScope())->getVariants(),
912: )->getReturnType()->getIterableValueType());
913: $isTraversable = true;
914: if (!$valueType instanceof MixedType || $valueType->isExplicitMixed()) {
915: return $valueType;
916: }
917: }
918:
919: $extraOffsetAccessible = $this->isExtraOffsetAccessibleClass()->yes();
920: if (!$extraOffsetAccessible && $this->isInstanceOf(Traversable::class)->yes()) {
921: $isTraversable = true;
922: $tValue = $this->getTemplateType(Traversable::class, 'TValue');
923: if (!$tValue instanceof ErrorType) {
924: if (!$tValue instanceof MixedType || $tValue->isExplicitMixed()) {
925: return $tValue;
926: }
927: }
928: }
929:
930: if ($this->isInstanceOf(Iterator::class)->yes()) {
931: return RecursionGuard::run($this, fn (): Type => ParametersAcceptorSelector::selectSingle(
932: $this->getMethod('current', new OutOfClassScope())->getVariants(),
933: )->getReturnType());
934: }
935:
936: if ($extraOffsetAccessible) {
937: return new MixedType(true);
938: }
939:
940: if ($isTraversable) {
941: return new MixedType();
942: }
943:
944: return new ErrorType();
945: }
946:
947: public function getFirstIterableValueType(): Type
948: {
949: return $this->getIterableValueType();
950: }
951:
952: public function getLastIterableValueType(): Type
953: {
954: return $this->getIterableValueType();
955: }
956:
957: public function isNull(): TrinaryLogic
958: {
959: return TrinaryLogic::createNo();
960: }
961:
962: public function isConstantValue(): TrinaryLogic
963: {
964: return TrinaryLogic::createNo();
965: }
966:
967: public function isConstantScalarValue(): TrinaryLogic
968: {
969: return TrinaryLogic::createNo();
970: }
971:
972: public function getConstantScalarTypes(): array
973: {
974: return [];
975: }
976:
977: public function getConstantScalarValues(): array
978: {
979: return [];
980: }
981:
982: public function isTrue(): TrinaryLogic
983: {
984: return TrinaryLogic::createNo();
985: }
986:
987: public function isFalse(): TrinaryLogic
988: {
989: return TrinaryLogic::createNo();
990: }
991:
992: public function isBoolean(): TrinaryLogic
993: {
994: return TrinaryLogic::createNo();
995: }
996:
997: public function isFloat(): TrinaryLogic
998: {
999: return TrinaryLogic::createNo();
1000: }
1001:
1002: public function isInteger(): TrinaryLogic
1003: {
1004: return TrinaryLogic::createNo();
1005: }
1006:
1007: public function isString(): TrinaryLogic
1008: {
1009: return TrinaryLogic::createNo();
1010: }
1011:
1012: public function isNumericString(): TrinaryLogic
1013: {
1014: return TrinaryLogic::createNo();
1015: }
1016:
1017: public function isNonEmptyString(): TrinaryLogic
1018: {
1019: return TrinaryLogic::createNo();
1020: }
1021:
1022: public function isNonFalsyString(): TrinaryLogic
1023: {
1024: return TrinaryLogic::createNo();
1025: }
1026:
1027: public function isLiteralString(): TrinaryLogic
1028: {
1029: return TrinaryLogic::createNo();
1030: }
1031:
1032: public function isClassStringType(): TrinaryLogic
1033: {
1034: return TrinaryLogic::createNo();
1035: }
1036:
1037: public function getClassStringObjectType(): Type
1038: {
1039: return new ErrorType();
1040: }
1041:
1042: public function getObjectTypeOrClassStringObjectType(): Type
1043: {
1044: return $this;
1045: }
1046:
1047: public function isVoid(): TrinaryLogic
1048: {
1049: return TrinaryLogic::createNo();
1050: }
1051:
1052: public function isScalar(): TrinaryLogic
1053: {
1054: return TrinaryLogic::createNo();
1055: }
1056:
1057: public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType
1058: {
1059: if ($type->isTrue()->yes()) {
1060: return new ConstantBooleanType(true);
1061: }
1062:
1063: return $type->isFalse()->yes()
1064: ? new ConstantBooleanType(false)
1065: : new BooleanType();
1066: }
1067:
1068: private function isExtraOffsetAccessibleClass(): TrinaryLogic
1069: {
1070: $classReflection = $this->getClassReflection();
1071: if ($classReflection === null) {
1072: return TrinaryLogic::createMaybe();
1073: }
1074:
1075: foreach (self::EXTRA_OFFSET_CLASSES as $extraOffsetClass) {
1076: if ($classReflection->getName() === $extraOffsetClass) {
1077: return TrinaryLogic::createYes();
1078: }
1079: if ($classReflection->isSubclassOf($extraOffsetClass)) {
1080: return TrinaryLogic::createYes();
1081: }
1082: }
1083:
1084: if ($classReflection->isInterface()) {
1085: return TrinaryLogic::createMaybe();
1086: }
1087:
1088: if ($classReflection->isFinal()) {
1089: return TrinaryLogic::createNo();
1090: }
1091:
1092: return TrinaryLogic::createMaybe();
1093: }
1094:
1095: public function isOffsetAccessible(): TrinaryLogic
1096: {
1097: return $this->isInstanceOf(ArrayAccess::class)->or(
1098: $this->isExtraOffsetAccessibleClass(),
1099: );
1100: }
1101:
1102: public function hasOffsetValueType(Type $offsetType): TrinaryLogic
1103: {
1104: if ($this->isInstanceOf(ArrayAccess::class)->yes()) {
1105: $acceptedOffsetType = RecursionGuard::run($this, function (): Type {
1106: $parameters = ParametersAcceptorSelector::selectSingle($this->getMethod('offsetSet', new OutOfClassScope())->getVariants())->getParameters();
1107: if (count($parameters) < 2) {
1108: throw new ShouldNotHappenException(sprintf(
1109: 'Method %s::%s() has less than 2 parameters.',
1110: $this->className,
1111: 'offsetSet',
1112: ));
1113: }
1114:
1115: $offsetParameter = $parameters[0];
1116:
1117: return $offsetParameter->getType();
1118: });
1119:
1120: if ($acceptedOffsetType->isSuperTypeOf($offsetType)->no()) {
1121: return TrinaryLogic::createNo();
1122: }
1123:
1124: return TrinaryLogic::createMaybe();
1125: }
1126:
1127: return $this->isExtraOffsetAccessibleClass()
1128: ->and(TrinaryLogic::createMaybe());
1129: }
1130:
1131: public function getOffsetValueType(Type $offsetType): Type
1132: {
1133: if (!$this->isExtraOffsetAccessibleClass()->no()) {
1134: return new MixedType();
1135: }
1136:
1137: if ($this->isInstanceOf(ArrayAccess::class)->yes()) {
1138: return RecursionGuard::run($this, fn (): Type => ParametersAcceptorSelector::selectSingle($this->getMethod('offsetGet', new OutOfClassScope())->getVariants())->getReturnType());
1139: }
1140:
1141: return new ErrorType();
1142: }
1143:
1144: public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type
1145: {
1146: if ($this->isOffsetAccessible()->no()) {
1147: return new ErrorType();
1148: }
1149:
1150: if ($this->isInstanceOf(ArrayAccess::class)->yes()) {
1151: $acceptedValueType = new NeverType();
1152: $acceptedOffsetType = RecursionGuard::run($this, function () use (&$acceptedValueType): Type {
1153: $parameters = ParametersAcceptorSelector::selectSingle($this->getMethod('offsetSet', new OutOfClassScope())->getVariants())->getParameters();
1154: if (count($parameters) < 2) {
1155: throw new ShouldNotHappenException(sprintf(
1156: 'Method %s::%s() has less than 2 parameters.',
1157: $this->className,
1158: 'offsetSet',
1159: ));
1160: }
1161:
1162: $offsetParameter = $parameters[0];
1163: $acceptedValueType = $parameters[1]->getType();
1164:
1165: return $offsetParameter->getType();
1166: });
1167:
1168: if ($offsetType === null) {
1169: $offsetType = new NullType();
1170: }
1171:
1172: if (
1173: (!$offsetType instanceof MixedType && !$acceptedOffsetType->isSuperTypeOf($offsetType)->yes())
1174: || (!$valueType instanceof MixedType && !$acceptedValueType->isSuperTypeOf($valueType)->yes())
1175: ) {
1176: return new ErrorType();
1177: }
1178: }
1179:
1180: // in the future we may return intersection of $this and OffsetAccessibleType()
1181: return $this;
1182: }
1183:
1184: public function setExistingOffsetValueType(Type $offsetType, Type $valueType): Type
1185: {
1186: if ($this->isOffsetAccessible()->no()) {
1187: return new ErrorType();
1188: }
1189:
1190: return $this;
1191: }
1192:
1193: public function unsetOffset(Type $offsetType): Type
1194: {
1195: if ($this->isOffsetAccessible()->no()) {
1196: return new ErrorType();
1197: }
1198:
1199: return $this;
1200: }
1201:
1202: public function getEnumCases(): array
1203: {
1204: $classReflection = $this->getClassReflection();
1205: if ($classReflection === null) {
1206: return [];
1207: }
1208:
1209: if (!$classReflection->isEnum()) {
1210: return [];
1211: }
1212:
1213: $subtracted = [];
1214: if ($this->subtractedType !== null) {
1215: foreach ($this->subtractedType->getEnumCases() as $enumCase) {
1216: $subtracted[$enumCase->getEnumCaseName()] = true;
1217: }
1218: }
1219:
1220: $cases = [];
1221: $className = $classReflection->getName();
1222: foreach ($classReflection->getEnumCases() as $enumCase) {
1223: if (array_key_exists($enumCase->getName(), $subtracted)) {
1224: continue;
1225: }
1226: $cases[] = new EnumCaseObjectType($className, $enumCase->getName(), $classReflection);
1227: }
1228:
1229: return $cases;
1230: }
1231:
1232: public function isCallable(): TrinaryLogic
1233: {
1234: $parametersAcceptors = $this->findCallableParametersAcceptors();
1235: if ($parametersAcceptors === null) {
1236: return TrinaryLogic::createNo();
1237: }
1238:
1239: if (
1240: count($parametersAcceptors) === 1
1241: && $parametersAcceptors[0] instanceof TrivialParametersAcceptor
1242: ) {
1243: return TrinaryLogic::createMaybe();
1244: }
1245:
1246: return TrinaryLogic::createYes();
1247: }
1248:
1249: public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array
1250: {
1251: if ($this->className === Closure::class) {
1252: return [new TrivialParametersAcceptor('Closure')];
1253: }
1254: $parametersAcceptors = $this->findCallableParametersAcceptors();
1255: if ($parametersAcceptors === null) {
1256: throw new ShouldNotHappenException();
1257: }
1258:
1259: return $parametersAcceptors;
1260: }
1261:
1262: /**
1263: * @return CallableParametersAcceptor[]|null
1264: */
1265: private function findCallableParametersAcceptors(): ?array
1266: {
1267: $classReflection = $this->getClassReflection();
1268: if ($classReflection === null) {
1269: return [new TrivialParametersAcceptor()];
1270: }
1271:
1272: if ($classReflection->hasNativeMethod('__invoke')) {
1273: $method = $this->getMethod('__invoke', new OutOfClassScope());
1274: return FunctionCallableVariant::createFromVariants(
1275: $method,
1276: $method->getVariants(),
1277: );
1278: }
1279:
1280: if (!$classReflection->getNativeReflection()->isFinal()) {
1281: return [new TrivialParametersAcceptor()];
1282: }
1283:
1284: return null;
1285: }
1286:
1287: public function isCloneable(): TrinaryLogic
1288: {
1289: return TrinaryLogic::createYes();
1290: }
1291:
1292: /**
1293: * @param mixed[] $properties
1294: */
1295: public static function __set_state(array $properties): Type
1296: {
1297: return new self(
1298: $properties['className'],
1299: $properties['subtractedType'] ?? null,
1300: );
1301: }
1302:
1303: public function isInstanceOf(string $className): TrinaryLogic
1304: {
1305: $classReflection = $this->getClassReflection();
1306: if ($classReflection === null) {
1307: return TrinaryLogic::createMaybe();
1308: }
1309:
1310: if ($classReflection->getName() === $className || $classReflection->isSubclassOf($className)) {
1311: return TrinaryLogic::createYes();
1312: }
1313:
1314: $reflectionProvider = ReflectionProviderStaticAccessor::getInstance();
1315: if ($reflectionProvider->hasClass($className)) {
1316: $thatClassReflection = $reflectionProvider->getClass($className);
1317: if ($thatClassReflection->isFinal()) {
1318: return TrinaryLogic::createNo();
1319: }
1320: }
1321:
1322: if ($classReflection->isInterface()) {
1323: return TrinaryLogic::createMaybe();
1324: }
1325:
1326: return TrinaryLogic::createNo();
1327: }
1328:
1329: public function subtract(Type $type): Type
1330: {
1331: if ($this->subtractedType !== null) {
1332: $type = TypeCombinator::union($this->subtractedType, $type);
1333: }
1334:
1335: return $this->changeSubtractedType($type);
1336: }
1337:
1338: public function getTypeWithoutSubtractedType(): Type
1339: {
1340: return $this->changeSubtractedType(null);
1341: }
1342:
1343: public function changeSubtractedType(?Type $subtractedType): Type
1344: {
1345: if ($subtractedType !== null) {
1346: $classReflection = $this->getClassReflection();
1347: $allowedSubTypesList = $classReflection !== null ? $classReflection->getAllowedSubTypes() : null;
1348: if ($allowedSubTypesList !== null) {
1349: $allowedSubTypes = [];
1350: foreach ($allowedSubTypesList as $allowedSubType) {
1351: $allowedSubTypes[$allowedSubType->describe(VerbosityLevel::precise())] = $allowedSubType;
1352: }
1353:
1354: $originalAllowedSubTypes = $allowedSubTypes;
1355: $subtractedSubTypes = [];
1356:
1357: $subtractedTypesList = TypeUtils::flattenTypes($subtractedType);
1358: $subtractedTypes = [];
1359: foreach ($subtractedTypesList as $type) {
1360: $subtractedTypes[$type->describe(VerbosityLevel::precise())] = $type;
1361: }
1362:
1363: foreach ($subtractedTypes as $subType) {
1364: foreach ($allowedSubTypes as $description => $allowedSubType) {
1365: if ($subType->equals($allowedSubType)) {
1366: $subtractedSubTypes[$description] = $subType;
1367: unset($allowedSubTypes[$description]);
1368: continue 2;
1369: }
1370: }
1371:
1372: return new self($this->className, $subtractedType);
1373: }
1374:
1375: if (count($allowedSubTypes) === 1) {
1376: return array_values($allowedSubTypes)[0];
1377: }
1378:
1379: $subtractedSubTypes = array_values($subtractedSubTypes);
1380: $subtractedSubTypesCount = count($subtractedSubTypes);
1381: if ($subtractedSubTypesCount === count($originalAllowedSubTypes)) {
1382: return new NeverType();
1383: }
1384:
1385: if ($subtractedSubTypesCount === 0) {
1386: return new self($this->className);
1387: }
1388:
1389: if ($subtractedSubTypesCount === 1) {
1390: return new self($this->className, $subtractedSubTypes[0]);
1391: }
1392:
1393: return new self($this->className, new UnionType($subtractedSubTypes));
1394: }
1395: }
1396:
1397: if ($this->subtractedType === null && $subtractedType === null) {
1398: return $this;
1399: }
1400:
1401: return new self($this->className, $subtractedType);
1402: }
1403:
1404: public function getSubtractedType(): ?Type
1405: {
1406: return $this->subtractedType;
1407: }
1408:
1409: public function traverse(callable $cb): Type
1410: {
1411: $subtractedType = $this->subtractedType !== null ? $cb($this->subtractedType) : null;
1412:
1413: if ($subtractedType !== $this->subtractedType) {
1414: return new self(
1415: $this->className,
1416: $subtractedType,
1417: );
1418: }
1419:
1420: return $this;
1421: }
1422:
1423: public function traverseSimultaneously(Type $right, callable $cb): Type
1424: {
1425: if ($this->subtractedType === null) {
1426: return $this;
1427: }
1428:
1429: return new self($this->className);
1430: }
1431:
1432: public function getNakedClassReflection(): ?ClassReflection
1433: {
1434: if ($this->classReflection !== null) {
1435: return $this->classReflection;
1436: }
1437:
1438: $reflectionProvider = ReflectionProviderStaticAccessor::getInstance();
1439: if (!$reflectionProvider->hasClass($this->className)) {
1440: return null;
1441: }
1442:
1443: return $reflectionProvider->getClass($this->className);
1444: }
1445:
1446: public function getClassReflection(): ?ClassReflection
1447: {
1448: if ($this->classReflection !== null) {
1449: return $this->classReflection;
1450: }
1451:
1452: $reflectionProvider = ReflectionProviderStaticAccessor::getInstance();
1453: if (!$reflectionProvider->hasClass($this->className)) {
1454: return null;
1455: }
1456:
1457: $classReflection = $reflectionProvider->getClass($this->className);
1458: if ($classReflection->isGeneric()) {
1459: return $classReflection->withTypes(array_values($classReflection->getTemplateTypeMap()->map(static fn (): Type => new ErrorType())->getTypes()));
1460: }
1461:
1462: return $classReflection;
1463: }
1464:
1465: /**
1466: * @return self|null
1467: */
1468: public function getAncestorWithClassName(string $className): ?TypeWithClassName
1469: {
1470: if ($this->className === $className) {
1471: return $this;
1472: }
1473:
1474: if ($this->classReflection !== null && $className === $this->classReflection->getName()) {
1475: return $this;
1476: }
1477:
1478: if (array_key_exists($className, $this->currentAncestors)) {
1479: return $this->currentAncestors[$className];
1480: }
1481:
1482: $description = $this->describeCache();
1483: if (
1484: array_key_exists($description, self::$ancestors)
1485: && array_key_exists($className, self::$ancestors[$description])
1486: ) {
1487: return self::$ancestors[$description][$className];
1488: }
1489:
1490: $reflectionProvider = ReflectionProviderStaticAccessor::getInstance();
1491: if (!$reflectionProvider->hasClass($className)) {
1492: return self::$ancestors[$description][$className] = $this->currentAncestors[$className] = null;
1493: }
1494: $theirReflection = $reflectionProvider->getClass($className);
1495:
1496: $thisReflection = $this->getClassReflection();
1497: if ($thisReflection === null) {
1498: return self::$ancestors[$description][$className] = $this->currentAncestors[$className] = null;
1499: }
1500: if ($theirReflection->getName() === $thisReflection->getName()) {
1501: return self::$ancestors[$description][$className] = $this->currentAncestors[$className] = $this;
1502: }
1503:
1504: foreach ($this->getInterfaces() as $interface) {
1505: $ancestor = $interface->getAncestorWithClassName($className);
1506: if ($ancestor !== null) {
1507: return self::$ancestors[$description][$className] = $this->currentAncestors[$className] = $ancestor;
1508: }
1509: }
1510:
1511: $parent = $this->getParent();
1512: if ($parent !== null) {
1513: $ancestor = $parent->getAncestorWithClassName($className);
1514: if ($ancestor !== null) {
1515: return self::$ancestors[$description][$className] = $this->currentAncestors[$className] = $ancestor;
1516: }
1517: }
1518:
1519: return self::$ancestors[$description][$className] = $this->currentAncestors[$className] = null;
1520: }
1521:
1522: private function getParent(): ?ObjectType
1523: {
1524: if ($this->cachedParent !== null) {
1525: return $this->cachedParent;
1526: }
1527: $thisReflection = $this->getClassReflection();
1528: if ($thisReflection === null) {
1529: return null;
1530: }
1531:
1532: $parentReflection = $thisReflection->getParentClass();
1533: if ($parentReflection === null) {
1534: return null;
1535: }
1536:
1537: return $this->cachedParent = self::createFromReflection($parentReflection);
1538: }
1539:
1540: /** @return ObjectType[] */
1541: private function getInterfaces(): array
1542: {
1543: if ($this->cachedInterfaces !== null) {
1544: return $this->cachedInterfaces;
1545: }
1546: $thisReflection = $this->getClassReflection();
1547: if ($thisReflection === null) {
1548: return $this->cachedInterfaces = [];
1549: }
1550:
1551: return $this->cachedInterfaces = array_map(static fn (ClassReflection $interfaceReflection): self => self::createFromReflection($interfaceReflection), $thisReflection->getInterfaces());
1552: }
1553:
1554: public function tryRemove(Type $typeToRemove): ?Type
1555: {
1556: if ($this->getClassName() === DateTimeInterface::class) {
1557: if ($typeToRemove instanceof ObjectType && $typeToRemove->getClassName() === DateTimeImmutable::class) {
1558: return new ObjectType(DateTime::class);
1559: }
1560:
1561: if ($typeToRemove instanceof ObjectType && $typeToRemove->getClassName() === DateTime::class) {
1562: return new ObjectType(DateTimeImmutable::class);
1563: }
1564: }
1565:
1566: if ($this->getClassName() === Throwable::class) {
1567: if ($typeToRemove instanceof ObjectType && $typeToRemove->getClassName() === Error::class) {
1568: return new ObjectType(Exception::class); // phpcs:ignore SlevomatCodingStandard.Exceptions.ReferenceThrowableOnly.ReferencedGeneralException
1569: }
1570:
1571: if ($typeToRemove instanceof ObjectType && $typeToRemove->getClassName() === Exception::class) { // phpcs:ignore SlevomatCodingStandard.Exceptions.ReferenceThrowableOnly.ReferencedGeneralException
1572: return new ObjectType(Error::class);
1573: }
1574: }
1575:
1576: if ($this->isSuperTypeOf($typeToRemove)->yes()) {
1577: return $this->subtract($typeToRemove);
1578: }
1579:
1580: return null;
1581: }
1582:
1583: public function getFiniteTypes(): array
1584: {
1585: return $this->getEnumCases();
1586: }
1587:
1588: public function exponentiate(Type $exponent): Type
1589: {
1590: $object = new ObjectWithoutClassType();
1591: if (!$exponent instanceof NeverType && !$object->isSuperTypeOf($this)->no() && !$object->isSuperTypeOf($exponent)->no()) {
1592: return TypeCombinator::union($this, $exponent);
1593: }
1594: return new ErrorType();
1595: }
1596:
1597: public function toPhpDocNode(): TypeNode
1598: {
1599: return new IdentifierTypeNode($this->getClassName());
1600: }
1601:
1602: }
1603: