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