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