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