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