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