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