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 toAbsoluteNumber(): Type
567: {
568: return $this->toNumber()->toAbsoluteNumber();
569: }
570:
571: public function toInteger(): Type
572: {
573: if ($this->isInstanceOf('SimpleXMLElement')->yes()) {
574: return new IntegerType();
575: }
576:
577: if (in_array($this->getClassName(), ['CurlHandle', 'CurlMultiHandle'], true)) {
578: return new IntegerType();
579: }
580:
581: return new ErrorType();
582: }
583:
584: public function toFloat(): Type
585: {
586: if ($this->isInstanceOf('SimpleXMLElement')->yes()) {
587: return new FloatType();
588: }
589: return new ErrorType();
590: }
591:
592: public function toString(): Type
593: {
594: $classReflection = $this->getClassReflection();
595: if ($classReflection === null) {
596: return new ErrorType();
597: }
598:
599: if ($classReflection->hasNativeMethod('__toString')) {
600: return ParametersAcceptorSelector::selectSingle($this->getMethod('__toString', new OutOfClassScope())->getVariants())->getReturnType();
601: }
602:
603: return new ErrorType();
604: }
605:
606: public function toArray(): Type
607: {
608: $classReflection = $this->getClassReflection();
609: if ($classReflection === null) {
610: return new ArrayType(new MixedType(), new MixedType());
611: }
612:
613: $reflectionProvider = ReflectionProviderStaticAccessor::getInstance();
614:
615: if (
616: !$classReflection->getNativeReflection()->isUserDefined()
617: || UniversalObjectCratesClassReflectionExtension::isUniversalObjectCrate(
618: $reflectionProvider,
619: Broker::getInstance()->getUniversalObjectCratesClasses(),
620: $classReflection,
621: )
622: ) {
623: return new ArrayType(new MixedType(), new MixedType());
624: }
625: $arrayKeys = [];
626: $arrayValues = [];
627:
628: $isFinal = $classReflection->isFinal();
629:
630: do {
631: foreach ($classReflection->getNativeReflection()->getProperties() as $nativeProperty) {
632: if ($nativeProperty->isStatic()) {
633: continue;
634: }
635:
636: $declaringClass = $reflectionProvider->getClass($nativeProperty->getDeclaringClass()->getName());
637: $property = $declaringClass->getNativeProperty($nativeProperty->getName());
638:
639: $keyName = $nativeProperty->getName();
640: if ($nativeProperty->isPrivate()) {
641: $keyName = sprintf(
642: "\0%s\0%s",
643: $declaringClass->getName(),
644: $keyName,
645: );
646: } elseif ($nativeProperty->isProtected()) {
647: $keyName = sprintf(
648: "\0*\0%s",
649: $keyName,
650: );
651: }
652:
653: $arrayKeys[] = new ConstantStringType($keyName);
654: $arrayValues[] = $property->getReadableType();
655: }
656:
657: $classReflection = $classReflection->getParentClass();
658: } while ($classReflection !== null);
659:
660: if (!$isFinal && count($arrayKeys) === 0) {
661: return new ArrayType(new MixedType(), new MixedType());
662: }
663:
664: return new ConstantArrayType($arrayKeys, $arrayValues);
665: }
666:
667: public function toArrayKey(): Type
668: {
669: return $this->toString();
670: }
671:
672: public function toBoolean(): BooleanType
673: {
674: if ($this->isInstanceOf('SimpleXMLElement')->yes()) {
675: return new BooleanType();
676: }
677:
678: return new ConstantBooleanType(true);
679: }
680:
681: public function isObject(): TrinaryLogic
682: {
683: return TrinaryLogic::createYes();
684: }
685:
686: public function isEnum(): TrinaryLogic
687: {
688: $classReflection = $this->getClassReflection();
689: if ($classReflection === null) {
690: return TrinaryLogic::createMaybe();
691: }
692:
693: return TrinaryLogic::createFromBoolean($classReflection->isEnum());
694: }
695:
696: public function canAccessProperties(): TrinaryLogic
697: {
698: return TrinaryLogic::createYes();
699: }
700:
701: public function canCallMethods(): TrinaryLogic
702: {
703: if (strtolower($this->className) === 'stdclass') {
704: return TrinaryLogic::createNo();
705: }
706:
707: return TrinaryLogic::createYes();
708: }
709:
710: public function hasMethod(string $methodName): TrinaryLogic
711: {
712: $classReflection = $this->getClassReflection();
713: if ($classReflection === null) {
714: return TrinaryLogic::createMaybe();
715: }
716:
717: if ($classReflection->hasMethod($methodName)) {
718: return TrinaryLogic::createYes();
719: }
720:
721: if ($classReflection->isFinal()) {
722: return TrinaryLogic::createNo();
723: }
724:
725: return TrinaryLogic::createMaybe();
726: }
727:
728: public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): ExtendedMethodReflection
729: {
730: return $this->getUnresolvedMethodPrototype($methodName, $scope)->getTransformedMethod();
731: }
732:
733: public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection
734: {
735: if (!$scope->isInClass()) {
736: $canCallMethod = 'no';
737: } else {
738: $canCallMethod = $scope->getClassReflection()->getName();
739: }
740: $description = $this->describeCache();
741: if (isset(self::$methods[$description][$methodName][$canCallMethod])) {
742: return self::$methods[$description][$methodName][$canCallMethod];
743: }
744:
745: $nakedClassReflection = $this->getNakedClassReflection();
746: if ($nakedClassReflection === null) {
747: throw new ClassNotFoundException($this->className);
748: }
749:
750: if (!$nakedClassReflection->hasNativeMethod($methodName)) {
751: $nakedClassReflection = $this->getClassReflection();
752: }
753:
754: if ($nakedClassReflection === null) {
755: throw new ClassNotFoundException($this->className);
756: }
757:
758: $method = $nakedClassReflection->getMethod($methodName, $scope);
759:
760: $ancestor = $this->getAncestorWithClassName($method->getDeclaringClass()->getName());
761: $resolvedClassReflection = null;
762: if ($ancestor !== null) {
763: $resolvedClassReflection = $ancestor->getClassReflection();
764: if ($ancestor !== $this) {
765: $method = $ancestor->getUnresolvedMethodPrototype($methodName, $scope)->getNakedMethod();
766: }
767: }
768: if ($resolvedClassReflection === null) {
769: $resolvedClassReflection = $method->getDeclaringClass();
770: }
771:
772: return self::$methods[$description][$methodName][$canCallMethod] = new CalledOnTypeUnresolvedMethodPrototypeReflection(
773: $method,
774: $resolvedClassReflection,
775: true,
776: $this,
777: );
778: }
779:
780: public function canAccessConstants(): TrinaryLogic
781: {
782: return TrinaryLogic::createYes();
783: }
784:
785: public function hasConstant(string $constantName): TrinaryLogic
786: {
787: $class = $this->getClassReflection();
788: if ($class === null) {
789: return TrinaryLogic::createNo();
790: }
791:
792: return TrinaryLogic::createFromBoolean(
793: $class->hasConstant($constantName),
794: );
795: }
796:
797: public function getConstant(string $constantName): ConstantReflection
798: {
799: $class = $this->getClassReflection();
800: if ($class === null) {
801: throw new ClassNotFoundException($this->className);
802: }
803:
804: return $class->getConstant($constantName);
805: }
806:
807: public function getTemplateType(string $ancestorClassName, string $templateTypeName): Type
808: {
809: $classReflection = $this->getClassReflection();
810: if ($classReflection === null) {
811: return new ErrorType();
812: }
813:
814: $ancestorClassReflection = $classReflection->getAncestorWithClassName($ancestorClassName);
815: if ($ancestorClassReflection === null) {
816: return new ErrorType();
817: }
818:
819: $activeTemplateTypeMap = $ancestorClassReflection->getPossiblyIncompleteActiveTemplateTypeMap();
820: $type = $activeTemplateTypeMap->getType($templateTypeName);
821: if ($type === null) {
822: return new ErrorType();
823: }
824: if ($type instanceof ErrorType) {
825: $templateTypeMap = $ancestorClassReflection->getTemplateTypeMap();
826: $templateType = $templateTypeMap->getType($templateTypeName);
827: if ($templateType === null) {
828: return $type;
829: }
830:
831: $bound = TemplateTypeHelper::resolveToBounds($templateType);
832: if ($bound instanceof MixedType && $bound->isExplicitMixed()) {
833: return new MixedType(false);
834: }
835:
836: return $bound;
837: }
838:
839: return $type;
840: }
841:
842: public function getConstantStrings(): array
843: {
844: return [];
845: }
846:
847: public function isIterable(): TrinaryLogic
848: {
849: return $this->isInstanceOf(Traversable::class);
850: }
851:
852: public function isIterableAtLeastOnce(): TrinaryLogic
853: {
854: return $this->isInstanceOf(Traversable::class)
855: ->and(TrinaryLogic::createMaybe());
856: }
857:
858: public function getArraySize(): Type
859: {
860: if ($this->isInstanceOf(Countable::class)->no()) {
861: return new ErrorType();
862: }
863:
864: return IntegerRangeType::fromInterval(0, null);
865: }
866:
867: public function getIterableKeyType(): Type
868: {
869: $isTraversable = false;
870: if ($this->isInstanceOf(IteratorAggregate::class)->yes()) {
871: $keyType = RecursionGuard::run($this, fn (): Type => ParametersAcceptorSelector::selectSingle(
872: $this->getMethod('getIterator', new OutOfClassScope())->getVariants(),
873: )->getReturnType()->getIterableKeyType());
874: $isTraversable = true;
875: if (!$keyType instanceof MixedType || $keyType->isExplicitMixed()) {
876: return $keyType;
877: }
878: }
879:
880: $extraOffsetAccessible = $this->isExtraOffsetAccessibleClass()->yes();
881: if (!$extraOffsetAccessible && $this->isInstanceOf(Traversable::class)->yes()) {
882: $isTraversable = true;
883: $tKey = $this->getTemplateType(Traversable::class, 'TKey');
884: if (!$tKey instanceof ErrorType) {
885: if (!$tKey instanceof MixedType || $tKey->isExplicitMixed()) {
886: return $tKey;
887: }
888: }
889: }
890:
891: if ($this->isInstanceOf(Iterator::class)->yes()) {
892: return RecursionGuard::run($this, fn (): Type => ParametersAcceptorSelector::selectSingle(
893: $this->getMethod('key', new OutOfClassScope())->getVariants(),
894: )->getReturnType());
895: }
896:
897: if ($extraOffsetAccessible) {
898: return new MixedType(true);
899: }
900:
901: if ($isTraversable) {
902: return new MixedType();
903: }
904:
905: return new ErrorType();
906: }
907:
908: public function getFirstIterableKeyType(): Type
909: {
910: return $this->getIterableKeyType();
911: }
912:
913: public function getLastIterableKeyType(): Type
914: {
915: return $this->getIterableKeyType();
916: }
917:
918: public function getIterableValueType(): Type
919: {
920: $isTraversable = false;
921: if ($this->isInstanceOf(IteratorAggregate::class)->yes()) {
922: $valueType = RecursionGuard::run($this, fn (): Type => ParametersAcceptorSelector::selectSingle(
923: $this->getMethod('getIterator', new OutOfClassScope())->getVariants(),
924: )->getReturnType()->getIterableValueType());
925: $isTraversable = true;
926: if (!$valueType instanceof MixedType || $valueType->isExplicitMixed()) {
927: return $valueType;
928: }
929: }
930:
931: $extraOffsetAccessible = $this->isExtraOffsetAccessibleClass()->yes();
932: if (!$extraOffsetAccessible && $this->isInstanceOf(Traversable::class)->yes()) {
933: $isTraversable = true;
934: $tValue = $this->getTemplateType(Traversable::class, 'TValue');
935: if (!$tValue instanceof ErrorType) {
936: if (!$tValue instanceof MixedType || $tValue->isExplicitMixed()) {
937: return $tValue;
938: }
939: }
940: }
941:
942: if ($this->isInstanceOf(Iterator::class)->yes()) {
943: return RecursionGuard::run($this, fn (): Type => ParametersAcceptorSelector::selectSingle(
944: $this->getMethod('current', new OutOfClassScope())->getVariants(),
945: )->getReturnType());
946: }
947:
948: if ($extraOffsetAccessible) {
949: return new MixedType(true);
950: }
951:
952: if ($isTraversable) {
953: return new MixedType();
954: }
955:
956: return new ErrorType();
957: }
958:
959: public function getFirstIterableValueType(): Type
960: {
961: return $this->getIterableValueType();
962: }
963:
964: public function getLastIterableValueType(): Type
965: {
966: return $this->getIterableValueType();
967: }
968:
969: public function isNull(): TrinaryLogic
970: {
971: return TrinaryLogic::createNo();
972: }
973:
974: public function isConstantValue(): TrinaryLogic
975: {
976: return TrinaryLogic::createNo();
977: }
978:
979: public function isConstantScalarValue(): TrinaryLogic
980: {
981: return TrinaryLogic::createNo();
982: }
983:
984: public function getConstantScalarTypes(): array
985: {
986: return [];
987: }
988:
989: public function getConstantScalarValues(): array
990: {
991: return [];
992: }
993:
994: public function isTrue(): TrinaryLogic
995: {
996: return TrinaryLogic::createNo();
997: }
998:
999: public function isFalse(): TrinaryLogic
1000: {
1001: return TrinaryLogic::createNo();
1002: }
1003:
1004: public function isBoolean(): TrinaryLogic
1005: {
1006: return TrinaryLogic::createNo();
1007: }
1008:
1009: public function isFloat(): TrinaryLogic
1010: {
1011: return TrinaryLogic::createNo();
1012: }
1013:
1014: public function isInteger(): TrinaryLogic
1015: {
1016: return TrinaryLogic::createNo();
1017: }
1018:
1019: public function isString(): TrinaryLogic
1020: {
1021: return TrinaryLogic::createNo();
1022: }
1023:
1024: public function isNumericString(): TrinaryLogic
1025: {
1026: return TrinaryLogic::createNo();
1027: }
1028:
1029: public function isNonEmptyString(): TrinaryLogic
1030: {
1031: return TrinaryLogic::createNo();
1032: }
1033:
1034: public function isNonFalsyString(): TrinaryLogic
1035: {
1036: return TrinaryLogic::createNo();
1037: }
1038:
1039: public function isLiteralString(): TrinaryLogic
1040: {
1041: return TrinaryLogic::createNo();
1042: }
1043:
1044: public function isClassStringType(): TrinaryLogic
1045: {
1046: return TrinaryLogic::createNo();
1047: }
1048:
1049: public function getClassStringObjectType(): Type
1050: {
1051: return new ErrorType();
1052: }
1053:
1054: public function getObjectTypeOrClassStringObjectType(): Type
1055: {
1056: return $this;
1057: }
1058:
1059: public function isVoid(): TrinaryLogic
1060: {
1061: return TrinaryLogic::createNo();
1062: }
1063:
1064: public function isScalar(): TrinaryLogic
1065: {
1066: return TrinaryLogic::createNo();
1067: }
1068:
1069: public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType
1070: {
1071: if ($type->isTrue()->yes()) {
1072: return new ConstantBooleanType(true);
1073: }
1074:
1075: return $type->isFalse()->yes()
1076: ? new ConstantBooleanType(false)
1077: : new BooleanType();
1078: }
1079:
1080: private function isExtraOffsetAccessibleClass(): TrinaryLogic
1081: {
1082: $classReflection = $this->getClassReflection();
1083: if ($classReflection === null) {
1084: return TrinaryLogic::createMaybe();
1085: }
1086:
1087: foreach (self::EXTRA_OFFSET_CLASSES as $extraOffsetClass) {
1088: if ($classReflection->getName() === $extraOffsetClass) {
1089: return TrinaryLogic::createYes();
1090: }
1091: if ($classReflection->isSubclassOf($extraOffsetClass)) {
1092: return TrinaryLogic::createYes();
1093: }
1094: }
1095:
1096: if ($classReflection->isInterface()) {
1097: return TrinaryLogic::createMaybe();
1098: }
1099:
1100: if ($classReflection->isFinal()) {
1101: return TrinaryLogic::createNo();
1102: }
1103:
1104: return TrinaryLogic::createMaybe();
1105: }
1106:
1107: public function isOffsetAccessible(): TrinaryLogic
1108: {
1109: return $this->isInstanceOf(ArrayAccess::class)->or(
1110: $this->isExtraOffsetAccessibleClass(),
1111: );
1112: }
1113:
1114: public function isOffsetAccessLegal(): TrinaryLogic
1115: {
1116: return $this->isOffsetAccessible();
1117: }
1118:
1119: public function hasOffsetValueType(Type $offsetType): TrinaryLogic
1120: {
1121: if ($this->isInstanceOf(ArrayAccess::class)->yes()) {
1122: $acceptedOffsetType = RecursionGuard::run($this, function (): Type {
1123: $parameters = ParametersAcceptorSelector::selectSingle($this->getMethod('offsetSet', new OutOfClassScope())->getVariants())->getParameters();
1124: if (count($parameters) < 2) {
1125: throw new ShouldNotHappenException(sprintf(
1126: 'Method %s::%s() has less than 2 parameters.',
1127: $this->className,
1128: 'offsetSet',
1129: ));
1130: }
1131:
1132: $offsetParameter = $parameters[0];
1133:
1134: return $offsetParameter->getType();
1135: });
1136:
1137: if ($acceptedOffsetType->isSuperTypeOf($offsetType)->no()) {
1138: return TrinaryLogic::createNo();
1139: }
1140:
1141: return TrinaryLogic::createMaybe();
1142: }
1143:
1144: return $this->isExtraOffsetAccessibleClass()
1145: ->and(TrinaryLogic::createMaybe());
1146: }
1147:
1148: public function getOffsetValueType(Type $offsetType): Type
1149: {
1150: if (!$this->isExtraOffsetAccessibleClass()->no()) {
1151: return new MixedType();
1152: }
1153:
1154: if ($this->isInstanceOf(ArrayAccess::class)->yes()) {
1155: return RecursionGuard::run($this, fn (): Type => ParametersAcceptorSelector::selectSingle($this->getMethod('offsetGet', new OutOfClassScope())->getVariants())->getReturnType());
1156: }
1157:
1158: return new ErrorType();
1159: }
1160:
1161: public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type
1162: {
1163: if ($this->isOffsetAccessible()->no()) {
1164: return new ErrorType();
1165: }
1166:
1167: if ($this->isInstanceOf(ArrayAccess::class)->yes()) {
1168: $acceptedValueType = new NeverType();
1169: $acceptedOffsetType = RecursionGuard::run($this, function () use (&$acceptedValueType): Type {
1170: $parameters = ParametersAcceptorSelector::selectSingle($this->getMethod('offsetSet', new OutOfClassScope())->getVariants())->getParameters();
1171: if (count($parameters) < 2) {
1172: throw new ShouldNotHappenException(sprintf(
1173: 'Method %s::%s() has less than 2 parameters.',
1174: $this->className,
1175: 'offsetSet',
1176: ));
1177: }
1178:
1179: $offsetParameter = $parameters[0];
1180: $acceptedValueType = $parameters[1]->getType();
1181:
1182: return $offsetParameter->getType();
1183: });
1184:
1185: if ($offsetType === null) {
1186: $offsetType = new NullType();
1187: }
1188:
1189: if (
1190: (!$offsetType instanceof MixedType && !$acceptedOffsetType->isSuperTypeOf($offsetType)->yes())
1191: || (!$valueType instanceof MixedType && !$acceptedValueType->isSuperTypeOf($valueType)->yes())
1192: ) {
1193: return new ErrorType();
1194: }
1195: }
1196:
1197: // in the future we may return intersection of $this and OffsetAccessibleType()
1198: return $this;
1199: }
1200:
1201: public function setExistingOffsetValueType(Type $offsetType, Type $valueType): Type
1202: {
1203: if ($this->isOffsetAccessible()->no()) {
1204: return new ErrorType();
1205: }
1206:
1207: return $this;
1208: }
1209:
1210: public function unsetOffset(Type $offsetType): Type
1211: {
1212: if ($this->isOffsetAccessible()->no()) {
1213: return new ErrorType();
1214: }
1215:
1216: return $this;
1217: }
1218:
1219: public function getEnumCases(): array
1220: {
1221: $classReflection = $this->getClassReflection();
1222: if ($classReflection === null) {
1223: return [];
1224: }
1225:
1226: if (!$classReflection->isEnum()) {
1227: return [];
1228: }
1229:
1230: $cacheKey = $this->describeCache();
1231: if (array_key_exists($cacheKey, self::$enumCases)) {
1232: return self::$enumCases[$cacheKey];
1233: }
1234:
1235: $className = $classReflection->getName();
1236:
1237: if ($this->subtractedType !== null) {
1238: $subtractedEnumCaseNames = [];
1239:
1240: foreach ($this->subtractedType->getEnumCases() as $subtractedCase) {
1241: $subtractedEnumCaseNames[$subtractedCase->getEnumCaseName()] = true;
1242: }
1243:
1244: $cases = [];
1245: foreach ($classReflection->getEnumCases() as $enumCase) {
1246: if (array_key_exists($enumCase->getName(), $subtractedEnumCaseNames)) {
1247: continue;
1248: }
1249: $cases[] = new EnumCaseObjectType($className, $enumCase->getName(), $classReflection);
1250: }
1251: } else {
1252: $cases = [];
1253: foreach ($classReflection->getEnumCases() as $enumCase) {
1254: $cases[] = new EnumCaseObjectType($className, $enumCase->getName(), $classReflection);
1255: }
1256: }
1257:
1258: return self::$enumCases[$cacheKey] = $cases;
1259: }
1260:
1261: public function isCallable(): TrinaryLogic
1262: {
1263: $parametersAcceptors = RecursionGuard::run($this, fn () => $this->findCallableParametersAcceptors());
1264: if ($parametersAcceptors === null) {
1265: return TrinaryLogic::createNo();
1266: }
1267: if ($parametersAcceptors instanceof ErrorType) {
1268: return TrinaryLogic::createNo();
1269: }
1270:
1271: if (
1272: count($parametersAcceptors) === 1
1273: && $parametersAcceptors[0] instanceof TrivialParametersAcceptor
1274: ) {
1275: return TrinaryLogic::createMaybe();
1276: }
1277:
1278: return TrinaryLogic::createYes();
1279: }
1280:
1281: public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array
1282: {
1283: if ($this->className === Closure::class) {
1284: return [new TrivialParametersAcceptor('Closure')];
1285: }
1286: $parametersAcceptors = $this->findCallableParametersAcceptors();
1287: if ($parametersAcceptors === null) {
1288: throw new ShouldNotHappenException();
1289: }
1290:
1291: return $parametersAcceptors;
1292: }
1293:
1294: /**
1295: * @return CallableParametersAcceptor[]|null
1296: */
1297: private function findCallableParametersAcceptors(): ?array
1298: {
1299: $classReflection = $this->getClassReflection();
1300: if ($classReflection === null) {
1301: return [new TrivialParametersAcceptor()];
1302: }
1303:
1304: if ($classReflection->hasNativeMethod('__invoke')) {
1305: $method = $this->getMethod('__invoke', new OutOfClassScope());
1306: return FunctionCallableVariant::createFromVariants(
1307: $method,
1308: $method->getVariants(),
1309: );
1310: }
1311:
1312: if (!$classReflection->getNativeReflection()->isFinal()) {
1313: return [new TrivialParametersAcceptor()];
1314: }
1315:
1316: return null;
1317: }
1318:
1319: public function isCloneable(): TrinaryLogic
1320: {
1321: return TrinaryLogic::createYes();
1322: }
1323:
1324: /**
1325: * @param mixed[] $properties
1326: */
1327: public static function __set_state(array $properties): Type
1328: {
1329: return new self(
1330: $properties['className'],
1331: $properties['subtractedType'] ?? null,
1332: );
1333: }
1334:
1335: public function isInstanceOf(string $className): TrinaryLogic
1336: {
1337: $classReflection = $this->getClassReflection();
1338: if ($classReflection === null) {
1339: return TrinaryLogic::createMaybe();
1340: }
1341:
1342: if ($classReflection->getName() === $className || $classReflection->isSubclassOf($className)) {
1343: return TrinaryLogic::createYes();
1344: }
1345:
1346: $reflectionProvider = ReflectionProviderStaticAccessor::getInstance();
1347: if ($reflectionProvider->hasClass($className)) {
1348: $thatClassReflection = $reflectionProvider->getClass($className);
1349: if ($thatClassReflection->isFinal()) {
1350: return TrinaryLogic::createNo();
1351: }
1352: }
1353:
1354: if ($classReflection->isInterface()) {
1355: return TrinaryLogic::createMaybe();
1356: }
1357:
1358: return TrinaryLogic::createNo();
1359: }
1360:
1361: public function subtract(Type $type): Type
1362: {
1363: if ($this->subtractedType !== null) {
1364: $type = TypeCombinator::union($this->subtractedType, $type);
1365: }
1366:
1367: return $this->changeSubtractedType($type);
1368: }
1369:
1370: public function getTypeWithoutSubtractedType(): Type
1371: {
1372: return $this->changeSubtractedType(null);
1373: }
1374:
1375: public function changeSubtractedType(?Type $subtractedType): Type
1376: {
1377: if ($subtractedType !== null) {
1378: $classReflection = $this->getClassReflection();
1379: $allowedSubTypes = $classReflection !== null ? $classReflection->getAllowedSubTypes() : null;
1380: if ($allowedSubTypes !== null) {
1381: $preciseVerbosity = VerbosityLevel::precise();
1382:
1383: $originalAllowedSubTypes = $allowedSubTypes;
1384: $subtractedSubTypes = [];
1385:
1386: $subtractedTypes = TypeUtils::flattenTypes($subtractedType);
1387: foreach ($subtractedTypes as $subType) {
1388: foreach ($allowedSubTypes as $key => $allowedSubType) {
1389: if ($subType->equals($allowedSubType)) {
1390: $description = $allowedSubType->describe($preciseVerbosity);
1391: $subtractedSubTypes[$description] = $subType;
1392: unset($allowedSubTypes[$key]);
1393: continue 2;
1394: }
1395: }
1396:
1397: return new self($this->className, $subtractedType);
1398: }
1399:
1400: if (count($allowedSubTypes) === 1) {
1401: return array_values($allowedSubTypes)[0];
1402: }
1403:
1404: $subtractedSubTypes = array_values($subtractedSubTypes);
1405: $subtractedSubTypesCount = count($subtractedSubTypes);
1406: if ($subtractedSubTypesCount === count($originalAllowedSubTypes)) {
1407: return new NeverType();
1408: }
1409:
1410: if ($subtractedSubTypesCount === 0) {
1411: return new self($this->className);
1412: }
1413:
1414: if ($subtractedSubTypesCount === 1) {
1415: return new self($this->className, $subtractedSubTypes[0]);
1416: }
1417:
1418: return new self($this->className, new UnionType($subtractedSubTypes));
1419: }
1420: }
1421:
1422: if ($this->subtractedType === null && $subtractedType === null) {
1423: return $this;
1424: }
1425:
1426: return new self($this->className, $subtractedType);
1427: }
1428:
1429: public function getSubtractedType(): ?Type
1430: {
1431: return $this->subtractedType;
1432: }
1433:
1434: public function traverse(callable $cb): Type
1435: {
1436: $subtractedType = $this->subtractedType !== null ? $cb($this->subtractedType) : null;
1437:
1438: if ($subtractedType !== $this->subtractedType) {
1439: return new self(
1440: $this->className,
1441: $subtractedType,
1442: );
1443: }
1444:
1445: return $this;
1446: }
1447:
1448: public function traverseSimultaneously(Type $right, callable $cb): Type
1449: {
1450: if ($this->subtractedType === null) {
1451: return $this;
1452: }
1453:
1454: return new self($this->className);
1455: }
1456:
1457: public function getNakedClassReflection(): ?ClassReflection
1458: {
1459: if ($this->classReflection !== null) {
1460: return $this->classReflection;
1461: }
1462:
1463: $reflectionProvider = ReflectionProviderStaticAccessor::getInstance();
1464: if (!$reflectionProvider->hasClass($this->className)) {
1465: return null;
1466: }
1467:
1468: return $reflectionProvider->getClass($this->className);
1469: }
1470:
1471: public function getClassReflection(): ?ClassReflection
1472: {
1473: if ($this->classReflection !== null) {
1474: return $this->classReflection;
1475: }
1476:
1477: $reflectionProvider = ReflectionProviderStaticAccessor::getInstance();
1478: if (!$reflectionProvider->hasClass($this->className)) {
1479: return null;
1480: }
1481:
1482: $classReflection = $reflectionProvider->getClass($this->className);
1483: if ($classReflection->isGeneric()) {
1484: return $classReflection->withTypes(array_values($classReflection->getTemplateTypeMap()->map(static fn (): Type => new ErrorType())->getTypes()));
1485: }
1486:
1487: return $classReflection;
1488: }
1489:
1490: /**
1491: * @return self|null
1492: */
1493: public function getAncestorWithClassName(string $className): ?TypeWithClassName
1494: {
1495: if ($this->className === $className) {
1496: return $this;
1497: }
1498:
1499: if ($this->classReflection !== null && $className === $this->classReflection->getName()) {
1500: return $this;
1501: }
1502:
1503: if (array_key_exists($className, $this->currentAncestors)) {
1504: return $this->currentAncestors[$className];
1505: }
1506:
1507: $description = $this->describeCache();
1508: if (
1509: array_key_exists($description, self::$ancestors)
1510: && array_key_exists($className, self::$ancestors[$description])
1511: ) {
1512: return self::$ancestors[$description][$className];
1513: }
1514:
1515: $reflectionProvider = ReflectionProviderStaticAccessor::getInstance();
1516: if (!$reflectionProvider->hasClass($className)) {
1517: return self::$ancestors[$description][$className] = $this->currentAncestors[$className] = null;
1518: }
1519: $theirReflection = $reflectionProvider->getClass($className);
1520:
1521: $thisReflection = $this->getClassReflection();
1522: if ($thisReflection === null) {
1523: return self::$ancestors[$description][$className] = $this->currentAncestors[$className] = null;
1524: }
1525: if ($theirReflection->getName() === $thisReflection->getName()) {
1526: return self::$ancestors[$description][$className] = $this->currentAncestors[$className] = $this;
1527: }
1528:
1529: foreach ($this->getInterfaces() as $interface) {
1530: $ancestor = $interface->getAncestorWithClassName($className);
1531: if ($ancestor !== null) {
1532: return self::$ancestors[$description][$className] = $this->currentAncestors[$className] = $ancestor;
1533: }
1534: }
1535:
1536: $parent = $this->getParent();
1537: if ($parent !== null) {
1538: $ancestor = $parent->getAncestorWithClassName($className);
1539: if ($ancestor !== null) {
1540: return self::$ancestors[$description][$className] = $this->currentAncestors[$className] = $ancestor;
1541: }
1542: }
1543:
1544: return self::$ancestors[$description][$className] = $this->currentAncestors[$className] = null;
1545: }
1546:
1547: private function getParent(): ?ObjectType
1548: {
1549: if ($this->cachedParent !== null) {
1550: return $this->cachedParent;
1551: }
1552: $thisReflection = $this->getClassReflection();
1553: if ($thisReflection === null) {
1554: return null;
1555: }
1556:
1557: $parentReflection = $thisReflection->getParentClass();
1558: if ($parentReflection === null) {
1559: return null;
1560: }
1561:
1562: return $this->cachedParent = self::createFromReflection($parentReflection);
1563: }
1564:
1565: /** @return ObjectType[] */
1566: private function getInterfaces(): array
1567: {
1568: if ($this->cachedInterfaces !== null) {
1569: return $this->cachedInterfaces;
1570: }
1571: $thisReflection = $this->getClassReflection();
1572: if ($thisReflection === null) {
1573: return $this->cachedInterfaces = [];
1574: }
1575:
1576: return $this->cachedInterfaces = array_map(static fn (ClassReflection $interfaceReflection): self => self::createFromReflection($interfaceReflection), $thisReflection->getInterfaces());
1577: }
1578:
1579: public function tryRemove(Type $typeToRemove): ?Type
1580: {
1581: if ($this->getClassName() === DateTimeInterface::class) {
1582: if ($typeToRemove instanceof ObjectType && $typeToRemove->getClassName() === DateTimeImmutable::class) {
1583: return new ObjectType(DateTime::class);
1584: }
1585:
1586: if ($typeToRemove instanceof ObjectType && $typeToRemove->getClassName() === DateTime::class) {
1587: return new ObjectType(DateTimeImmutable::class);
1588: }
1589: }
1590:
1591: if ($this->getClassName() === Throwable::class) {
1592: if ($typeToRemove instanceof ObjectType && $typeToRemove->getClassName() === Error::class) {
1593: return new ObjectType(Exception::class); // phpcs:ignore SlevomatCodingStandard.Exceptions.ReferenceThrowableOnly.ReferencedGeneralException
1594: }
1595:
1596: if ($typeToRemove instanceof ObjectType && $typeToRemove->getClassName() === Exception::class) { // phpcs:ignore SlevomatCodingStandard.Exceptions.ReferenceThrowableOnly.ReferencedGeneralException
1597: return new ObjectType(Error::class);
1598: }
1599: }
1600:
1601: if ($this->isSuperTypeOf($typeToRemove)->yes()) {
1602: return $this->subtract($typeToRemove);
1603: }
1604:
1605: return null;
1606: }
1607:
1608: public function getFiniteTypes(): array
1609: {
1610: return $this->getEnumCases();
1611: }
1612:
1613: public function exponentiate(Type $exponent): Type
1614: {
1615: $object = new ObjectWithoutClassType();
1616: if (!$exponent instanceof NeverType && !$object->isSuperTypeOf($this)->no() && !$object->isSuperTypeOf($exponent)->no()) {
1617: return TypeCombinator::union($this, $exponent);
1618: }
1619: return new ErrorType();
1620: }
1621:
1622: public function toPhpDocNode(): TypeNode
1623: {
1624: return new IdentifierTypeNode($this->getClassName());
1625: }
1626:
1627: }
1628: