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