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