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