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