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