1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Type;
4:
5: use ArrayAccess;
6: use ArrayObject;
7: use Closure;
8: use Countable;
9: use DateTimeInterface;
10: use Iterator;
11: use IteratorAggregate;
12: use PHPStan\Analyser\OutOfClassScope;
13: use PHPStan\Broker\ClassNotFoundException;
14: use PHPStan\Internal\LruCache;
15: use PHPStan\Php\PhpVersion;
16: use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
17: use PHPStan\PhpDocParser\Ast\Type\TypeNode;
18: use PHPStan\Reflection\Callables\CallableParametersAcceptor;
19: use PHPStan\Reflection\Callables\FunctionCallableVariant;
20: use PHPStan\Reflection\ClassConstantReflection;
21: use PHPStan\Reflection\ClassMemberAccessAnswerer;
22: use PHPStan\Reflection\ClassReflection;
23: use PHPStan\Reflection\Dummy\DummyPropertyReflection;
24: use PHPStan\Reflection\ExtendedMethodReflection;
25: use PHPStan\Reflection\ExtendedPropertyReflection;
26: use PHPStan\Reflection\Php\UniversalObjectCratesClassReflectionExtension;
27: use PHPStan\Reflection\ReflectionProvider;
28: use PHPStan\Reflection\ReflectionProviderStaticAccessor;
29: use PHPStan\Reflection\TrivialParametersAcceptor;
30: use PHPStan\Reflection\Type\CallbackUnresolvedPropertyPrototypeReflection;
31: use PHPStan\Reflection\Type\CalledOnTypeUnresolvedMethodPrototypeReflection;
32: use PHPStan\Reflection\Type\CalledOnTypeUnresolvedPropertyPrototypeReflection;
33: use PHPStan\Reflection\Type\UnionTypeUnresolvedPropertyPrototypeReflection;
34: use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection;
35: use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection;
36: use PHPStan\ShouldNotHappenException;
37: use PHPStan\TrinaryLogic;
38: use PHPStan\Turbo\ShadowedByTurboExtension;
39: use PHPStan\Type\Accessory\AccessoryLiteralStringType;
40: use PHPStan\Type\Accessory\AccessoryNonEmptyStringType;
41: use PHPStan\Type\Accessory\AccessoryNumericStringType;
42: use PHPStan\Type\Accessory\HasOffsetValueType;
43: use PHPStan\Type\Constant\ConstantArrayType;
44: use PHPStan\Type\Constant\ConstantBooleanType;
45: use PHPStan\Type\Constant\ConstantStringType;
46: use PHPStan\Type\Enum\EnumCaseObjectType;
47: use PHPStan\Type\Generic\GenericClassStringType;
48: use PHPStan\Type\Generic\GenericObjectType;
49: use PHPStan\Type\Generic\TemplateTypeHelper;
50: use PHPStan\Type\Generic\UnresolvedTemplateArgumentType;
51: use PHPStan\Type\Traits\MaybeIterableTypeTrait;
52: use PHPStan\Type\Traits\NonArrayTypeTrait;
53: use PHPStan\Type\Traits\NonGeneralizableTypeTrait;
54: use PHPStan\Type\Traits\NonGenericTypeTrait;
55: use PHPStan\Type\Traits\SubstractableTypeTrait;
56: use PHPStan\Type\Traits\UndecidedComparisonTypeTrait;
57: use Stringable;
58: use Throwable;
59: use Traversable;
60: use function array_key_exists;
61: use function array_map;
62: use function array_merge;
63: use function array_values;
64: use function count;
65: use function get_class;
66: use function implode;
67: use function in_array;
68: use function sprintf;
69: use function strtolower;
70:
71: /** @api */
72: #[InstanceofDeprecated(insteadUse: 'Type::isObject() or Type::getObjectClassNames()')]
73: #[ShadowedByTurboExtension(implementation: __DIR__ . '/../../turbo-ext/src/ObjectType.cpp')]
74: class ObjectType implements TypeWithClassName, SubtractableType
75: {
76:
77: use MaybeIterableTypeTrait;
78: use NonArrayTypeTrait;
79: use NonGenericTypeTrait;
80: use UndecidedComparisonTypeTrait;
81: use NonGeneralizableTypeTrait;
82: use SubstractableTypeTrait;
83:
84: private const DESCRIPTION_CACHE_LIMIT = 1024;
85:
86: private const EXTRA_OFFSET_CLASSES = [
87: 'DOMNamedNodeMap', // Only read and existence
88: 'Dom\NamedNodeMap', // Only read and existence
89: 'DOMNodeList', // Only read and existence
90: 'Dom\NodeList', // Only read and existence
91: 'Dom\HTMLCollection', // Only read and existence
92: 'Dom\DtdNamedNodeMap', // Only read and existence
93: 'PDORow', // Only read and existence
94: 'ResourceBundle', // Only read
95: 'FFI\CData', // Very funky and weird
96: 'SimpleXMLElement',
97: 'Threaded',
98: ];
99:
100: private ?Type $subtractedType;
101:
102: /** @var array<string, array<string, IsSuperTypeOfResult>> */
103: private static array $superTypes = [];
104:
105: private ?self $cachedParent = null;
106:
107: /** @var self[]|null */
108: private ?array $cachedInterfaces = null;
109:
110: /** @var array<string, array<string, array<string, UnresolvedMethodPrototypeReflection>>> */
111: private static array $methods = [];
112:
113: /** @var array<string, array<string, array<string, UnresolvedPropertyPrototypeReflection>>> */
114: private static array $properties = [];
115:
116: /** @var array<string, array<string, array<string, UnresolvedPropertyPrototypeReflection>>> */
117: private static array $instanceProperties = [];
118:
119: /** @var array<string, array<string, array<string, UnresolvedPropertyPrototypeReflection>>> */
120: private static array $staticProperties = [];
121:
122: /** @var array<string, array<string, self|null>> */
123: private static array $ancestors = [];
124:
125: /** @var array<string, self|null> */
126: private array $currentAncestors = [];
127:
128: private ?string $cachedDescription = null;
129:
130: /**
131: * The class name as the reflection spells it - the properly cased name, or the
132: * readable `class@anonymous...` form. Resolving it takes two ReflectionProvider
133: * round-trips and both the type-only and the value level need it, the latter on
134: * every operation RecursionGuard guards.
135: */
136: private ?string $cachedPreciseName = null;
137:
138: /**
139: * The reflection resolved on demand by getClassReflection(), kept apart from the one
140: * handed to the constructor. The constructor's reflection is part of the type's value
141: * — it can differ from what the provider would return (an anonymous class is identified
142: * by its start line, a final-by-keyword override changes subtyping) — and so it belongs
143: * in the cache key. A lazily fetched one is merely a cache: folding it into the key made
144: * describe(VerbosityLevel::cache()) depend on whether the fetch had happened yet, which
145: * made TypeCombinator::union() — which dedupes on that key — depend on cache warmth
146: * rather than on its arguments' values.
147: */
148: private ?ClassReflection $lazyClassReflection = null;
149:
150: /** @var array<string, list<EnumCaseObjectType>> */
151: private static array $enumCases = [];
152:
153: /** @var LruCache<true>|null shared LRU over the description keys of the static caches above */
154: private static ?LruCache $descriptionCacheOrder = null;
155:
156: /** consecutive lookups mostly touch the same description — skip the LRU bookkeeping for them */
157: private static ?string $lastTouchedDescription = null;
158:
159: /** @var array<string, ExtendedMethodReflection> */
160: private array $methodCache = [];
161:
162: /** @api */
163: public function __construct(
164: private string $className,
165: ?Type $subtractedType = null,
166: private ?ClassReflection $classReflection = null,
167: )
168: {
169: if ($subtractedType instanceof NeverType) {
170: $subtractedType = null;
171: }
172:
173: $this->subtractedType = $subtractedType;
174: }
175:
176: public static function resetCaches(): void
177: {
178: self::$superTypes = [];
179: self::$methods = [];
180: self::$properties = [];
181: self::$instanceProperties = [];
182: self::$staticProperties = [];
183: self::$ancestors = [];
184: self::$enumCases = [];
185: self::$descriptionCacheOrder = null;
186: self::$lastTouchedDescription = null;
187: }
188:
189: /**
190: * Moves the description to the most-recently-used position of the shared LRU
191: * governing the static caches above; evicts the least recently used description's
192: * entries from all of them once the limit is reached. Misses are pure recomputation.
193: */
194: private static function touchDescriptionCacheKey(string $description): void
195: {
196: if ($description === self::$lastTouchedDescription) {
197: return;
198: }
199: self::$lastTouchedDescription = $description;
200:
201: self::$descriptionCacheOrder ??= new LruCache(self::DESCRIPTION_CACHE_LIMIT);
202: if (self::$descriptionCacheOrder->get($description) !== null) {
203: return;
204: }
205:
206: foreach (self::$descriptionCacheOrder->set($description, true, 0) as $evictKey) {
207: unset(
208: self::$superTypes[$evictKey],
209: self::$methods[$evictKey],
210: self::$properties[$evictKey],
211: self::$instanceProperties[$evictKey],
212: self::$staticProperties[$evictKey],
213: self::$ancestors[$evictKey],
214: self::$enumCases[$evictKey],
215: );
216: }
217: }
218:
219: public function getClassName(): string
220: {
221: return $this->className;
222: }
223:
224: public function hasProperty(string $propertyName): TrinaryLogic
225: {
226: $classReflection = $this->getClassReflection();
227: if ($classReflection === null) {
228: return TrinaryLogic::createMaybe();
229: }
230:
231: $classHasProperty = RecursionGuard::run($this, static fn (): bool => $classReflection->hasProperty($propertyName));
232: if ($classHasProperty === true || $classHasProperty instanceof ErrorType) {
233: return TrinaryLogic::createYes();
234: }
235:
236: if ($classReflection->allowsDynamicProperties()) {
237: return TrinaryLogic::createMaybe();
238: }
239:
240: if (!$classReflection->isFinal()) {
241: return TrinaryLogic::createMaybe();
242: }
243:
244: return TrinaryLogic::createNo();
245: }
246:
247: public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
248: {
249: return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty();
250: }
251:
252: public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
253: {
254: if (!$scope->isInClass()) {
255: $canAccessProperty = 'no';
256: } else {
257: $canAccessProperty = $scope->getClassReflection()->getName();
258: }
259: $description = $this->describeCache();
260:
261: self::touchDescriptionCacheKey($description);
262:
263: if (isset(self::$properties[$description][$propertyName][$canAccessProperty])) {
264: return self::$properties[$description][$propertyName][$canAccessProperty];
265: }
266:
267: $nakedClassReflection = $this->getNakedClassReflection();
268: if ($nakedClassReflection === null) {
269: throw new ClassNotFoundException($this->className);
270: }
271:
272: if ($nakedClassReflection->isEnum()) {
273: if (
274: $propertyName === 'name'
275: || ($propertyName === 'value' && $nakedClassReflection->isBackedEnum())
276: ) {
277: $properties = [];
278: foreach ($this->getEnumCases() as $enumCase) {
279: $properties[] = $enumCase->getUnresolvedPropertyPrototype($propertyName, $scope);
280: }
281:
282: if (count($properties) > 0) {
283: if (count($properties) === 1) {
284: return $properties[0];
285: }
286:
287: return new UnionTypeUnresolvedPropertyPrototypeReflection($properties);
288: }
289: }
290: }
291:
292: if (!$nakedClassReflection->hasNativeProperty($propertyName)) {
293: $nakedClassReflection = $this->getClassReflection();
294: }
295:
296: if ($nakedClassReflection === null) {
297: throw new ClassNotFoundException($this->className);
298: }
299:
300: $property = RecursionGuard::run($this, static fn () => $nakedClassReflection->getProperty($propertyName, $scope));
301: if ($property instanceof ErrorType) {
302: $property = new DummyPropertyReflection($propertyName);
303:
304: return new CallbackUnresolvedPropertyPrototypeReflection(
305: $property,
306: $property->getDeclaringClass(),
307: false,
308: static fn (Type $type): Type => $type,
309: );
310: }
311:
312: $ancestor = $this->getAncestorWithClassName($property->getDeclaringClass()->getName());
313: $resolvedClassReflection = null;
314: if ($ancestor !== null && $ancestor->hasProperty($propertyName)->yes()) {
315: $resolvedClassReflection = $ancestor->getClassReflection();
316: if ($ancestor !== $this) {
317: $property = $ancestor->getUnresolvedPropertyPrototype($propertyName, $scope)->getNakedProperty();
318: }
319: }
320: if ($resolvedClassReflection === null) {
321: $resolvedClassReflection = $property->getDeclaringClass();
322: }
323:
324: return self::$properties[$description][$propertyName][$canAccessProperty] = new CalledOnTypeUnresolvedPropertyPrototypeReflection(
325: $property,
326: $resolvedClassReflection,
327: true,
328: $this,
329: );
330: }
331:
332: public function hasInstanceProperty(string $propertyName): TrinaryLogic
333: {
334: $classReflection = $this->getClassReflection();
335: if ($classReflection === null) {
336: return TrinaryLogic::createMaybe();
337: }
338:
339: $classHasProperty = RecursionGuard::run($this, static fn (): bool => $classReflection->hasInstanceProperty($propertyName));
340: if ($classHasProperty === true || $classHasProperty instanceof ErrorType) {
341: return TrinaryLogic::createYes();
342: }
343:
344: if ($classReflection->allowsDynamicProperties()) {
345: return TrinaryLogic::createMaybe();
346: }
347:
348: if (!$classReflection->isFinal()) {
349: return TrinaryLogic::createMaybe();
350: }
351:
352: return TrinaryLogic::createNo();
353: }
354:
355: public function getInstanceProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
356: {
357: return $this->getUnresolvedInstancePropertyPrototype($propertyName, $scope)->getTransformedProperty();
358: }
359:
360: public function getUnresolvedInstancePropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
361: {
362: if (!$scope->isInClass()) {
363: $canAccessProperty = 'no';
364: } else {
365: $canAccessProperty = $scope->getClassReflection()->getName();
366: }
367: $description = $this->describeCache();
368:
369: self::touchDescriptionCacheKey($description);
370:
371: if (isset(self::$instanceProperties[$description][$propertyName][$canAccessProperty])) {
372: return self::$instanceProperties[$description][$propertyName][$canAccessProperty];
373: }
374:
375: $nakedClassReflection = $this->getNakedClassReflection();
376: if ($nakedClassReflection === null) {
377: throw new ClassNotFoundException($this->className);
378: }
379:
380: if ($nakedClassReflection->isEnum()) {
381: if (
382: $propertyName === 'name'
383: || ($propertyName === 'value' && $nakedClassReflection->isBackedEnum())
384: ) {
385: $properties = [];
386: foreach ($this->getEnumCases() as $enumCase) {
387: $properties[] = $enumCase->getUnresolvedInstancePropertyPrototype($propertyName, $scope);
388: }
389:
390: if (count($properties) > 0) {
391: if (count($properties) === 1) {
392: return $properties[0];
393: }
394:
395: return new UnionTypeUnresolvedPropertyPrototypeReflection($properties);
396: }
397: }
398: }
399:
400: if (!$nakedClassReflection->hasNativeProperty($propertyName)) {
401: $nakedClassReflection = $this->getClassReflection();
402: }
403:
404: if ($nakedClassReflection === null) {
405: throw new ClassNotFoundException($this->className);
406: }
407:
408: $property = RecursionGuard::run($this, static fn () => $nakedClassReflection->getInstanceProperty($propertyName, $scope));
409: if ($property instanceof ErrorType) {
410: $property = new DummyPropertyReflection($propertyName);
411:
412: return new CallbackUnresolvedPropertyPrototypeReflection(
413: $property,
414: $property->getDeclaringClass(),
415: false,
416: static fn (Type $type): Type => $type,
417: );
418: }
419:
420: $ancestor = $this->getAncestorWithClassName($property->getDeclaringClass()->getName());
421: $resolvedClassReflection = null;
422: if ($ancestor !== null && $ancestor->hasInstanceProperty($propertyName)->yes()) {
423: $resolvedClassReflection = $ancestor->getClassReflection();
424: if ($ancestor !== $this) {
425: $property = $ancestor->getUnresolvedInstancePropertyPrototype($propertyName, $scope)->getNakedProperty();
426: }
427: }
428: if ($resolvedClassReflection === null) {
429: $resolvedClassReflection = $property->getDeclaringClass();
430: }
431:
432: return self::$instanceProperties[$description][$propertyName][$canAccessProperty] = new CalledOnTypeUnresolvedPropertyPrototypeReflection(
433: $property,
434: $resolvedClassReflection,
435: true,
436: $this,
437: );
438: }
439:
440: public function hasStaticProperty(string $propertyName): TrinaryLogic
441: {
442: $classReflection = $this->getClassReflection();
443: if ($classReflection === null) {
444: return TrinaryLogic::createMaybe();
445: }
446:
447: $classHasProperty = RecursionGuard::run($this, static fn (): bool => $classReflection->hasStaticProperty($propertyName));
448: if ($classHasProperty === true || $classHasProperty instanceof ErrorType) {
449: return TrinaryLogic::createYes();
450: }
451:
452: if (!$classReflection->isFinal()) {
453: return TrinaryLogic::createMaybe();
454: }
455:
456: return TrinaryLogic::createNo();
457: }
458:
459: public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
460: {
461: return $this->getUnresolvedStaticPropertyPrototype($propertyName, $scope)->getTransformedProperty();
462: }
463:
464: public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
465: {
466: if (!$scope->isInClass()) {
467: $canAccessProperty = 'no';
468: } else {
469: $canAccessProperty = $scope->getClassReflection()->getName();
470: }
471: $description = $this->describeCache();
472:
473: self::touchDescriptionCacheKey($description);
474:
475: if (isset(self::$staticProperties[$description][$propertyName][$canAccessProperty])) {
476: return self::$staticProperties[$description][$propertyName][$canAccessProperty];
477: }
478:
479: $nakedClassReflection = $this->getNakedClassReflection();
480: if ($nakedClassReflection === null) {
481: throw new ClassNotFoundException($this->className);
482: }
483:
484: if (!$nakedClassReflection->hasNativeProperty($propertyName)) {
485: $nakedClassReflection = $this->getClassReflection();
486: }
487:
488: if ($nakedClassReflection === null) {
489: throw new ClassNotFoundException($this->className);
490: }
491:
492: $property = RecursionGuard::run($this, static fn () => $nakedClassReflection->getStaticProperty($propertyName));
493: if ($property instanceof ErrorType) {
494: $property = new DummyPropertyReflection($propertyName);
495:
496: return new CallbackUnresolvedPropertyPrototypeReflection(
497: $property,
498: $property->getDeclaringClass(),
499: false,
500: static fn (Type $type): Type => $type,
501: );
502: }
503:
504: $ancestor = $this->getAncestorWithClassName($property->getDeclaringClass()->getName());
505: $resolvedClassReflection = null;
506: if ($ancestor !== null && $ancestor->hasStaticProperty($propertyName)->yes()) {
507: $resolvedClassReflection = $ancestor->getClassReflection();
508: if ($ancestor !== $this) {
509: $property = $ancestor->getUnresolvedStaticPropertyPrototype($propertyName, $scope)->getNakedProperty();
510: }
511: }
512: if ($resolvedClassReflection === null) {
513: $resolvedClassReflection = $property->getDeclaringClass();
514: }
515:
516: return self::$staticProperties[$description][$propertyName][$canAccessProperty] = new CalledOnTypeUnresolvedPropertyPrototypeReflection(
517: $property,
518: $resolvedClassReflection,
519: true,
520: $this,
521: );
522: }
523:
524: public function getReferencedClasses(): array
525: {
526: if ($this->className === '') {
527: return [];
528: }
529:
530: return [$this->className];
531: }
532:
533: public function getObjectClassNames(): array
534: {
535: if ($this->className === '') {
536: return [];
537: }
538: return [$this->className];
539: }
540:
541: public function getObjectClassReflections(): array
542: {
543: $classReflection = $this->getClassReflection();
544: if ($classReflection === null) {
545: return [];
546: }
547:
548: return [$classReflection];
549: }
550:
551: public function accepts(Type $type, bool $strictTypes): AcceptsResult
552: {
553: if ($type instanceof StaticType) {
554: return $this->checkSubclassAcceptability($type->getClassName());
555: }
556:
557: if ($type instanceof CompoundType) {
558: return $type->isAcceptedBy($this, $strictTypes);
559: }
560:
561: if ($type instanceof ClosureType) {
562: return new AcceptsResult($this->isInstanceOf(Closure::class), []);
563: }
564:
565: if ($type instanceof ObjectWithoutClassType) {
566: return AcceptsResult::createMaybe();
567: }
568:
569: $thatClassNames = $type->getObjectClassNames();
570: if (count($thatClassNames) > 1) {
571: throw new ShouldNotHappenException();
572: }
573:
574: if ($thatClassNames === []) {
575: return AcceptsResult::createNo();
576: }
577:
578: return $this->checkSubclassAcceptability($thatClassNames[0]);
579: }
580:
581: public function isSuperTypeOf(Type $type): IsSuperTypeOfResult
582: {
583: $thatClassNames = $type->getObjectClassNames();
584: if (!$type instanceof CompoundType && $thatClassNames === [] && !$type instanceof ObjectWithoutClassType) {
585: return IsSuperTypeOfResult::createNo();
586: }
587:
588: $thisDescription = $this->describeCache();
589:
590: if ($type instanceof self) {
591: $description = $type->describeCache();
592: } else {
593: $description = $type->describe(VerbosityLevel::cache());
594: }
595:
596: self::touchDescriptionCacheKey($thisDescription);
597:
598: if (isset(self::$superTypes[$thisDescription][$description])) {
599: return self::$superTypes[$thisDescription][$description];
600: }
601:
602: if ($type instanceof CompoundType) {
603: return self::$superTypes[$thisDescription][$description] = $type->isSubTypeOf($this);
604: }
605:
606: if ($type instanceof ClosureType) {
607: return self::$superTypes[$thisDescription][$description] = new IsSuperTypeOfResult($this->isInstanceOf(Closure::class), []);
608: }
609:
610: if ($type instanceof ObjectWithoutClassType) {
611: if ($type->getSubtractedType() !== null) {
612: $isSuperType = $type->getSubtractedType()->isSuperTypeOf($this);
613: if ($isSuperType->yes()) {
614: return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createNo();
615: }
616: }
617: return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createMaybe();
618: }
619:
620: $transformResult = static fn (IsSuperTypeOfResult $result) => $result;
621: if ($this->subtractedType !== null) {
622: $isSuperType = $this->subtractedType->isSuperTypeOf($type);
623: if ($isSuperType->yes()) {
624: return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createNo();
625: }
626: if ($isSuperType->maybe()) {
627: $transformResult = static fn (IsSuperTypeOfResult $result) => $result->and(IsSuperTypeOfResult::createMaybe());
628: }
629: }
630:
631: if (
632: $type instanceof SubtractableType
633: && $type->getSubtractedType() !== null
634: ) {
635: $isSuperType = $type->getSubtractedType()->isSuperTypeOf($this);
636: if ($isSuperType->yes()) {
637: return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createNo();
638: }
639: }
640:
641: $thisClassName = $this->className;
642: if (count($thatClassNames) > 1) {
643: throw new ShouldNotHappenException();
644: }
645:
646: $thisClassReflection = $this->getClassReflection();
647: $thatClassReflections = $type->getObjectClassReflections();
648: if (count($thatClassReflections) === 1) {
649: $thatClassReflection = $thatClassReflections[0];
650: } else {
651: $thatClassReflection = null;
652: }
653:
654: if ($thisClassReflection === null || $thatClassReflection === null) {
655: if ($thatClassNames[0] === $thisClassName) {
656: return self::$superTypes[$thisDescription][$description] = $transformResult(IsSuperTypeOfResult::createYes());
657: }
658: return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createMaybe();
659: }
660:
661: if ($thatClassNames[0] === $thisClassName) {
662: if ($thisClassReflection->getNativeReflection()->isFinal()) {
663: return self::$superTypes[$thisDescription][$description] = $transformResult(IsSuperTypeOfResult::createYes());
664: }
665:
666: if ($thisClassReflection->hasFinalByKeywordOverride()) {
667: if (!$thatClassReflection->hasFinalByKeywordOverride()) {
668: return self::$superTypes[$thisDescription][$description] = $transformResult(IsSuperTypeOfResult::createMaybe());
669: }
670: }
671:
672: return self::$superTypes[$thisDescription][$description] = $transformResult(IsSuperTypeOfResult::createYes());
673: }
674:
675: if ($thisClassReflection->isTrait() || $thatClassReflection->isTrait()) {
676: return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createNo();
677: }
678:
679: if ($thisClassReflection->getName() === $thatClassReflection->getName()) {
680: return self::$superTypes[$thisDescription][$description] = $transformResult(IsSuperTypeOfResult::createYes());
681: }
682:
683: if ($thatClassReflection->isSubclassOfClass($thisClassReflection)) {
684: return self::$superTypes[$thisDescription][$description] = $transformResult(IsSuperTypeOfResult::createYes());
685: }
686:
687: if ($thisClassReflection->isSubclassOfClass($thatClassReflection)) {
688: return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createMaybe();
689: }
690:
691: if ($thisClassReflection->isInterface() && !$thatClassReflection->isFinalByKeyword()) {
692: return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createMaybe();
693: }
694:
695: if ($thatClassReflection->isInterface() && !$thisClassReflection->isFinalByKeyword()) {
696: return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createMaybe();
697: }
698:
699: return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createNo();
700: }
701:
702: public function equals(Type $type): bool
703: {
704: if (get_class($type) !== static::class) {
705: return false;
706: }
707:
708: if ($this->className !== $type->className) {
709: return false;
710: }
711:
712: if ($this->subtractedType === null) {
713: return $type->subtractedType === null;
714: }
715:
716: if ($type->subtractedType === null) {
717: return false;
718: }
719:
720: return $this->subtractedType->equals($type->subtractedType);
721: }
722:
723: private function checkSubclassAcceptability(string $thatClass): AcceptsResult
724: {
725: if ($this->className === $thatClass) {
726: return AcceptsResult::createYes();
727: }
728:
729: $reflectionProvider = ReflectionProviderStaticAccessor::getInstance();
730:
731: if ($this->getClassReflection() === null || !$reflectionProvider->hasClass($thatClass)) {
732: return AcceptsResult::createNo();
733: }
734:
735: $thisReflection = $this->getClassReflection();
736: $thatReflection = $reflectionProvider->getClass($thatClass);
737:
738: if ($thisReflection->getName() === $thatReflection->getName()) {
739: // class alias
740: return AcceptsResult::createYes();
741: }
742:
743: if ($thisReflection->isInterface() && $thatReflection->isInterface()) {
744: return AcceptsResult::createFromBoolean(
745: $thatReflection->implementsInterface($thisReflection->getName()),
746: );
747: }
748:
749: return AcceptsResult::createFromBoolean(
750: $thatReflection->isSubclassOfClass($thisReflection),
751: );
752: }
753:
754: public function describe(VerbosityLevel $level): string
755: {
756: if ($this->cachedPreciseName !== null && ($level->isValue() || $level->isTypeOnly())) {
757: return $this->cachedPreciseName;
758: }
759:
760: $preciseNameCallback = function (): string {
761: if ($this->cachedPreciseName !== null) {
762: return $this->cachedPreciseName;
763: }
764:
765: $reflectionProvider = ReflectionProviderStaticAccessor::getInstance();
766: if (!$reflectionProvider->hasClass($this->className)) {
767: return $this->cachedPreciseName = $this->className;
768: }
769:
770: return $this->cachedPreciseName = $reflectionProvider->getClassName($this->className);
771: };
772:
773: $preciseWithSubtracted = fn (): string => $this->className . $this->describeSubtractedType($this->subtractedType, $level);
774:
775: return $level->handle(
776: $preciseNameCallback,
777: $preciseNameCallback,
778: $preciseWithSubtracted,
779: function () use ($preciseWithSubtracted): string {
780: $reflection = $this->classReflection;
781: $line = '';
782: if ($reflection !== null) {
783: $line .= '-';
784: $line .= (string) $reflection->getNativeReflection()->getStartLine();
785: $line .= '-';
786: }
787:
788: return $preciseWithSubtracted() . '-' . static::class . '-' . $line . $this->describeAdditionalCacheKey();
789: },
790: );
791: }
792:
793: protected function describeAdditionalCacheKey(): string
794: {
795: return '';
796: }
797:
798: private function describeCache(): string
799: {
800: if ($this->cachedDescription !== null) {
801: return $this->cachedDescription;
802: }
803:
804: if (static::class !== self::class) {
805: return $this->cachedDescription = $this->describe(VerbosityLevel::cache());
806: }
807:
808: $description = $this->className;
809:
810: if ($this instanceof GenericObjectType) {
811: $description .= '<';
812: $typeDescriptions = [];
813: foreach ($this->getTypes() as $type) {
814: $typeDescriptions[] = $type->describe(VerbosityLevel::cache());
815: }
816: $description .= '<' . implode(', ', $typeDescriptions) . '>';
817: }
818:
819: $description .= $this->describeSubtractedType($this->subtractedType, VerbosityLevel::cache());
820:
821: $reflection = $this->classReflection;
822: if ($reflection !== null) {
823: $description .= '-';
824: $description .= (string) $reflection->getNativeReflection()->getStartLine();
825: $description .= '-';
826:
827: if ($reflection->hasFinalByKeywordOverride()) {
828: $description .= 'f=' . ($reflection->isFinalByKeyword() ? 't' : 'f');
829: }
830: }
831:
832: return $this->cachedDescription = $description;
833: }
834:
835: public function toNumber(): Type
836: {
837: if (
838: $this->isInstanceOf('SimpleXMLElement')->yes()
839: || $this->isInstanceOf('GMP')->yes()
840: ) {
841: return new UnionType([
842: new FloatType(),
843: new IntegerType(),
844: ]);
845: }
846:
847: return new ErrorType();
848: }
849:
850: public function toBitwiseNotType(): Type
851: {
852: return new ErrorType();
853: }
854:
855: public function toGetClassResultType(): Type
856: {
857: return $this->getClassStringType();
858: }
859:
860: public function toClassConstantType(ReflectionProvider $reflectionProvider): Type
861: {
862: if ($reflectionProvider->hasClass($this->className)) {
863: $reflection = $reflectionProvider->getClass($this->className);
864: if ($reflection->isFinalByKeyword()) {
865: return new ConstantStringType($reflection->getName(), true);
866: }
867: }
868:
869: return new IntersectionType([$this->getClassStringType(), new AccessoryLiteralStringType()]);
870: }
871:
872: public function toObjectTypeForInstanceofCheck(): ClassNameToObjectTypeResult
873: {
874: return new ClassNameToObjectTypeResult($this, true);
875: }
876:
877: public function toObjectTypeForIsACheck(Type $objectOrClassType, bool $allowString, bool $allowSameClass): ClassNameToObjectTypeResult
878: {
879: if ($allowString) {
880: return new ClassNameToObjectTypeResult(
881: new UnionType([new ObjectWithoutClassType(), new ClassStringType()]),
882: false,
883: );
884: }
885:
886: return new ClassNameToObjectTypeResult(new ObjectWithoutClassType(), false);
887: }
888:
889: public function toAbsoluteNumber(): Type
890: {
891: return $this->toNumber()->toAbsoluteNumber();
892: }
893:
894: public function toInteger(): Type
895: {
896: if (
897: $this->isInstanceOf('SimpleXMLElement')->yes()
898: || $this->isInstanceOf('GMP')->yes()
899: ) {
900: return new IntegerType();
901: }
902:
903: if (in_array($this->getClassName(), ['CurlHandle', 'CurlMultiHandle'], true)) {
904: return new IntegerType();
905: }
906:
907: return new ErrorType();
908: }
909:
910: public function toFloat(): Type
911: {
912: if (
913: $this->isInstanceOf('SimpleXMLElement')->yes()
914: || $this->isInstanceOf('GMP')->yes()
915: ) {
916: return new FloatType();
917: }
918: return new ErrorType();
919: }
920:
921: public function toString(): Type
922: {
923: if (
924: $this->isInstanceOf('BcMath\Number')->yes()
925: || $this->isInstanceOf('GMP')->yes()
926: ) {
927: return new IntersectionType([
928: new StringType(),
929: new AccessoryNumericStringType(),
930: new AccessoryNonEmptyStringType(),
931: ]);
932: }
933:
934: $classReflection = $this->getClassReflection();
935: if ($classReflection === null) {
936: return new ErrorType();
937: }
938:
939: if ($classReflection->hasNativeMethod('__toString')) {
940: return $this->getMethod('__toString', new OutOfClassScope())->getOnlyVariant()->getReturnType();
941: }
942:
943: return new ErrorType();
944: }
945:
946: public function toArray(): Type
947: {
948: $classReflection = $this->getClassReflection();
949: if ($classReflection === null) {
950: return new ArrayType(new MixedType(), new MixedType());
951: }
952:
953: $reflectionProvider = ReflectionProviderStaticAccessor::getInstance();
954:
955: if (
956: !$classReflection->getNativeReflection()->isUserDefined()
957: || $classReflection->is(ArrayObject::class)
958: || UniversalObjectCratesClassReflectionExtension::isUniversalObjectCrate(
959: $reflectionProvider,
960: $classReflection,
961: )
962: ) {
963: return new ArrayType(new MixedType(), new MixedType());
964: }
965: $arrayKeys = [];
966: $arrayValues = [];
967:
968: $isFinal = $classReflection->isFinal();
969:
970: do {
971: foreach ($classReflection->getNativeReflection()->getProperties() as $nativeProperty) {
972: if ($nativeProperty->isStatic()) {
973: continue;
974: }
975:
976: $declaringClass = $reflectionProvider->getClass($nativeProperty->getDeclaringClass()->getName());
977: $property = $declaringClass->getNativeProperty($nativeProperty->getName());
978:
979: $keyName = $nativeProperty->getName();
980: if ($nativeProperty->isPrivate()) {
981: $keyName = sprintf(
982: "\0%s\0%s",
983: $declaringClass->getName(),
984: $keyName,
985: );
986: } elseif ($nativeProperty->isProtected()) {
987: $keyName = sprintf(
988: "\0*\0%s",
989: $keyName,
990: );
991: }
992:
993: $arrayKeys[] = new ConstantStringType($keyName);
994: $arrayValues[] = $property->getReadableType();
995: }
996:
997: $classReflection = $classReflection->getParentClass();
998: } while ($classReflection !== null);
999:
1000: if (!$isFinal) {
1001: if (count($arrayKeys) === 0 || count($arrayKeys) > 16) {
1002: return new ArrayType(new MixedType(), new MixedType());
1003: }
1004:
1005: $types = [new ArrayType(new MixedType(), new MixedType())];
1006: foreach ($arrayKeys as $i => $arrayKey) {
1007: $types[] = new HasOffsetValueType($arrayKey, $arrayValues[$i]);
1008: }
1009:
1010: return new IntersectionType($types);
1011: }
1012:
1013: return new ConstantArrayType($arrayKeys, $arrayValues);
1014: }
1015:
1016: public function toArrayKey(): Type
1017: {
1018: return new ErrorType();
1019: }
1020:
1021: public function toCoercedArgumentType(bool $strictTypes): Type
1022: {
1023: if (!$strictTypes) {
1024: $classReflection = $this->getClassReflection();
1025: if (
1026: $classReflection === null
1027: || !$classReflection->hasNativeMethod('__toString')
1028: ) {
1029: return $this;
1030: }
1031:
1032: return TypeCombinator::union($this, $this->toString());
1033: }
1034:
1035: return $this;
1036: }
1037:
1038: public function toBoolean(): BooleanType
1039: {
1040: if (
1041: $this->isInstanceOf('SimpleXMLElement')->yes()
1042: || $this->isInstanceOf('BcMath\Number')->yes()
1043: || $this->isInstanceOf('GMP')->yes()
1044: ) {
1045: return new BooleanType();
1046: }
1047:
1048: return new ConstantBooleanType(true);
1049: }
1050:
1051: public function isObject(): TrinaryLogic
1052: {
1053: return TrinaryLogic::createYes();
1054: }
1055:
1056: public function getClassStringType(): Type
1057: {
1058: return new GenericClassStringType($this);
1059: }
1060:
1061: public function isEnum(): TrinaryLogic
1062: {
1063: $classReflection = $this->getClassReflection();
1064: if ($classReflection === null) {
1065: return TrinaryLogic::createMaybe();
1066: }
1067:
1068: if (
1069: $classReflection->isEnum()
1070: || $classReflection->is('UnitEnum')
1071: ) {
1072: return TrinaryLogic::createYes();
1073: }
1074:
1075: if (
1076: $classReflection->isInterface()
1077: && !$classReflection->is(Stringable::class) // enums cannot have __toString
1078: && !$classReflection->is(Throwable::class) // enums cannot extend Exception/Error
1079: && !$classReflection->is(DateTimeInterface::class) // userland classes cannot extend DateTimeInterface
1080: ) {
1081: return TrinaryLogic::createMaybe();
1082: }
1083:
1084: return TrinaryLogic::createNo();
1085: }
1086:
1087: public function canAccessProperties(): TrinaryLogic
1088: {
1089: return TrinaryLogic::createYes();
1090: }
1091:
1092: public function canCallMethods(): TrinaryLogic
1093: {
1094: if (strtolower($this->className) === 'stdclass') {
1095: return TrinaryLogic::createNo();
1096: }
1097:
1098: return TrinaryLogic::createYes();
1099: }
1100:
1101: public function hasMethod(string $methodName): TrinaryLogic
1102: {
1103: $classReflection = $this->getClassReflection();
1104: if ($classReflection === null) {
1105: return TrinaryLogic::createMaybe();
1106: }
1107:
1108: if ($classReflection->hasMethod($methodName)) {
1109: return TrinaryLogic::createYes();
1110: }
1111:
1112: if ($classReflection->isFinal()) {
1113: return TrinaryLogic::createNo();
1114: }
1115:
1116: return TrinaryLogic::createMaybe();
1117: }
1118:
1119: public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): ExtendedMethodReflection
1120: {
1121: $key = $methodName;
1122: if ($scope->isInClass()) {
1123: $key = sprintf('%s-%s', $key, $scope->getClassReflection()->getCacheKey());
1124: }
1125: return $this->methodCache[$key] ??= $this->getUnresolvedMethodPrototype($methodName, $scope)->getTransformedMethod();
1126: }
1127:
1128: public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection
1129: {
1130: if (!$scope->isInClass()) {
1131: $canCallMethod = 'no';
1132: } else {
1133: $canCallMethod = $scope->getClassReflection()->getName();
1134: }
1135: $description = $this->describeCache();
1136: self::touchDescriptionCacheKey($description);
1137: if (isset(self::$methods[$description][$methodName][$canCallMethod])) {
1138: return self::$methods[$description][$methodName][$canCallMethod];
1139: }
1140:
1141: $nakedClassReflection = $this->getNakedClassReflection();
1142: if ($nakedClassReflection === null) {
1143: throw new ClassNotFoundException($this->className);
1144: }
1145:
1146: if (!$nakedClassReflection->hasNativeMethod($methodName)) {
1147: $nakedClassReflection = $this->getClassReflection();
1148: }
1149:
1150: if ($nakedClassReflection === null) {
1151: throw new ClassNotFoundException($this->className);
1152: }
1153:
1154: $method = $nakedClassReflection->getMethod($methodName, $scope);
1155:
1156: $ancestor = $this->getAncestorWithClassName($method->getDeclaringClass()->getName());
1157: $resolvedClassReflection = null;
1158: if ($ancestor !== null) {
1159: $resolvedClassReflection = $ancestor->getClassReflection();
1160: if ($ancestor !== $this) {
1161: $method = $ancestor->getUnresolvedMethodPrototype($methodName, $scope)->getNakedMethod();
1162: }
1163: }
1164: if ($resolvedClassReflection === null) {
1165: $resolvedClassReflection = $method->getDeclaringClass();
1166: }
1167:
1168: return self::$methods[$description][$methodName][$canCallMethod] = new CalledOnTypeUnresolvedMethodPrototypeReflection(
1169: $method,
1170: $resolvedClassReflection,
1171: true,
1172: $this,
1173: );
1174: }
1175:
1176: public function canAccessConstants(): TrinaryLogic
1177: {
1178: return TrinaryLogic::createYes();
1179: }
1180:
1181: public function hasConstant(string $constantName): TrinaryLogic
1182: {
1183: $classReflection = $this->getClassReflection();
1184: if ($classReflection === null) {
1185: return TrinaryLogic::createMaybe();
1186: }
1187:
1188: if ($classReflection->hasConstant($constantName)) {
1189: return TrinaryLogic::createYes();
1190: }
1191:
1192: if ($classReflection->isFinal()) {
1193: return TrinaryLogic::createNo();
1194: }
1195:
1196: return TrinaryLogic::createMaybe();
1197: }
1198:
1199: public function getConstant(string $constantName): ClassConstantReflection
1200: {
1201: $class = $this->getClassReflection();
1202: if ($class === null) {
1203: throw new ClassNotFoundException($this->className);
1204: }
1205:
1206: return $class->getConstant($constantName);
1207: }
1208:
1209: public function getTemplateType(string $ancestorClassName, string $templateTypeName): Type
1210: {
1211: $classReflection = $this->getClassReflection();
1212: if ($classReflection === null) {
1213: return new ErrorType();
1214: }
1215:
1216: $ancestorClassReflection = $classReflection->getAncestorWithClassName($ancestorClassName);
1217: if ($ancestorClassReflection === null) {
1218: return new ErrorType();
1219: }
1220:
1221: $activeTemplateTypeMap = $ancestorClassReflection->getPossiblyIncompleteActiveTemplateTypeMap();
1222: $type = $activeTemplateTypeMap->getType($templateTypeName);
1223: if ($type === null) {
1224: return new ErrorType();
1225: }
1226: if ($type instanceof UnresolvedTemplateArgumentType) {
1227: // read out of the object as a derived value - see TemplateTypeHelper::resolveTemplateTypes()
1228: return $type->getDelegate();
1229: }
1230: if ($type instanceof ErrorType) {
1231: $templateTypeMap = $ancestorClassReflection->getTemplateTypeMap();
1232: $templateType = $templateTypeMap->getType($templateTypeName);
1233: if ($templateType === null) {
1234: return $type;
1235: }
1236:
1237: $bound = TemplateTypeHelper::resolveToBounds($templateType);
1238: if ($bound instanceof MixedType && $bound->isExplicitMixed()) {
1239: return new MixedType(false);
1240: }
1241:
1242: return TemplateTypeHelper::resolveToDefaults($templateType);
1243: }
1244:
1245: return $type;
1246: }
1247:
1248: public function getConstantStrings(): array
1249: {
1250: return [];
1251: }
1252:
1253: public function isIterable(): TrinaryLogic
1254: {
1255: return $this->isInstanceOf(Traversable::class);
1256: }
1257:
1258: public function isIterableAtLeastOnce(): TrinaryLogic
1259: {
1260: return $this->isInstanceOf(Traversable::class)
1261: ->and(TrinaryLogic::createMaybe());
1262: }
1263:
1264: public function getArraySize(): Type
1265: {
1266: if ($this->isInstanceOf(Countable::class)->no()) {
1267: return new ErrorType();
1268: }
1269:
1270: if ($this->hasMethod('count')->yes() === false) {
1271: return IntegerRangeType::fromInterval(0, null);
1272: }
1273:
1274: return RecursionGuard::run($this, fn (): Type => $this->getMethod('count', new OutOfClassScope())->getOnlyVariant()->getReturnType());
1275: }
1276:
1277: public function getIterableKeyType(): Type
1278: {
1279: $isTraversable = false;
1280: if ($this->isInstanceOf(IteratorAggregate::class)->yes()) {
1281: $keyType = RecursionGuard::run($this, fn (): Type => $this->getMethod('getIterator', new OutOfClassScope())->getOnlyVariant()->getReturnType()->getIterableKeyType());
1282: $isTraversable = true;
1283: if (!$keyType instanceof MixedType || $keyType->isExplicitMixed()) {
1284: return $keyType;
1285: }
1286: }
1287:
1288: $extraOffsetAccessible = $this->isExtraOffsetAccessibleClass()->yes();
1289: if (!$extraOffsetAccessible && $this->isInstanceOf(Traversable::class)->yes()) {
1290: $isTraversable = true;
1291: $tKey = $this->getTemplateType(Traversable::class, 'TKey');
1292: if (!$tKey instanceof ErrorType) {
1293: if (!$tKey instanceof MixedType || $tKey->isExplicitMixed()) {
1294: return $tKey;
1295: }
1296: }
1297: }
1298:
1299: if ($this->isInstanceOf(Iterator::class)->yes()) {
1300: return RecursionGuard::run($this, fn (): Type => $this->getMethod('key', new OutOfClassScope())->getOnlyVariant()->getReturnType());
1301: }
1302:
1303: if ($extraOffsetAccessible) {
1304: return new MixedType(true);
1305: }
1306:
1307: if ($isTraversable) {
1308: return new MixedType();
1309: }
1310:
1311: return new ErrorType();
1312: }
1313:
1314: public function getFirstIterableKeyType(): Type
1315: {
1316: return $this->getIterableKeyType();
1317: }
1318:
1319: public function getLastIterableKeyType(): Type
1320: {
1321: return $this->getIterableKeyType();
1322: }
1323:
1324: public function getIterableValueType(): Type
1325: {
1326: $isTraversable = false;
1327: if ($this->isInstanceOf(IteratorAggregate::class)->yes()) {
1328: $valueType = RecursionGuard::run($this, fn (): Type => $this->getMethod('getIterator', new OutOfClassScope())->getOnlyVariant()->getReturnType()->getIterableValueType());
1329: $isTraversable = true;
1330: if (!$valueType instanceof MixedType || $valueType->isExplicitMixed()) {
1331: return $valueType;
1332: }
1333: }
1334:
1335: $extraOffsetAccessible = $this->isExtraOffsetAccessibleClass()->yes();
1336: if (!$extraOffsetAccessible && $this->isInstanceOf(Traversable::class)->yes()) {
1337: $isTraversable = true;
1338: $tValue = $this->getTemplateType(Traversable::class, 'TValue');
1339: if (!$tValue instanceof ErrorType) {
1340: if (!$tValue instanceof MixedType || $tValue->isExplicitMixed()) {
1341: return $tValue;
1342: }
1343: }
1344: }
1345:
1346: if ($this->isInstanceOf(Iterator::class)->yes()) {
1347: return RecursionGuard::run($this, fn (): Type => $this->getMethod('current', new OutOfClassScope())->getOnlyVariant()->getReturnType());
1348: }
1349:
1350: if ($extraOffsetAccessible) {
1351: return new MixedType(true);
1352: }
1353:
1354: if ($isTraversable) {
1355: return new MixedType();
1356: }
1357:
1358: return new ErrorType();
1359: }
1360:
1361: public function getFirstIterableValueType(): Type
1362: {
1363: return $this->getIterableValueType();
1364: }
1365:
1366: public function getLastIterableValueType(): Type
1367: {
1368: return $this->getIterableValueType();
1369: }
1370:
1371: public function isNull(): TrinaryLogic
1372: {
1373: return TrinaryLogic::createNo();
1374: }
1375:
1376: public function isConstantValue(): TrinaryLogic
1377: {
1378: return TrinaryLogic::createNo();
1379: }
1380:
1381: public function isConstantScalarValue(): TrinaryLogic
1382: {
1383: return TrinaryLogic::createNo();
1384: }
1385:
1386: public function getConstantScalarTypes(): array
1387: {
1388: return [];
1389: }
1390:
1391: public function getConstantScalarValues(): array
1392: {
1393: return [];
1394: }
1395:
1396: public function isTrue(): TrinaryLogic
1397: {
1398: return TrinaryLogic::createNo();
1399: }
1400:
1401: public function isFalse(): TrinaryLogic
1402: {
1403: return TrinaryLogic::createNo();
1404: }
1405:
1406: public function isBoolean(): TrinaryLogic
1407: {
1408: return TrinaryLogic::createNo();
1409: }
1410:
1411: public function isFloat(): TrinaryLogic
1412: {
1413: return TrinaryLogic::createNo();
1414: }
1415:
1416: public function isInteger(): TrinaryLogic
1417: {
1418: return TrinaryLogic::createNo();
1419: }
1420:
1421: public function isString(): TrinaryLogic
1422: {
1423: return TrinaryLogic::createNo();
1424: }
1425:
1426: public function isNumericString(): TrinaryLogic
1427: {
1428: return TrinaryLogic::createNo();
1429: }
1430:
1431: public function isDecimalIntegerString(): TrinaryLogic
1432: {
1433: return TrinaryLogic::createNo();
1434: }
1435:
1436: public function isNonEmptyString(): TrinaryLogic
1437: {
1438: return TrinaryLogic::createNo();
1439: }
1440:
1441: public function isNonFalsyString(): TrinaryLogic
1442: {
1443: return TrinaryLogic::createNo();
1444: }
1445:
1446: public function isLiteralString(): TrinaryLogic
1447: {
1448: return TrinaryLogic::createNo();
1449: }
1450:
1451: public function isLowercaseString(): TrinaryLogic
1452: {
1453: return TrinaryLogic::createNo();
1454: }
1455:
1456: public function isClassString(): TrinaryLogic
1457: {
1458: return TrinaryLogic::createNo();
1459: }
1460:
1461: public function isUppercaseString(): TrinaryLogic
1462: {
1463: return TrinaryLogic::createNo();
1464: }
1465:
1466: public function getClassStringObjectType(): Type
1467: {
1468: return new ErrorType();
1469: }
1470:
1471: public function getObjectTypeOrClassStringObjectType(): Type
1472: {
1473: return $this;
1474: }
1475:
1476: public function isVoid(): TrinaryLogic
1477: {
1478: return TrinaryLogic::createNo();
1479: }
1480:
1481: public function isScalar(): TrinaryLogic
1482: {
1483: return TrinaryLogic::createNo();
1484: }
1485:
1486: public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType
1487: {
1488: if ($type->isTrue()->yes()) {
1489: return new ConstantBooleanType(true);
1490: }
1491:
1492: return $type->isFalse()->yes()
1493: ? new ConstantBooleanType(false)
1494: : new BooleanType();
1495: }
1496:
1497: private function isExtraOffsetAccessibleClass(): TrinaryLogic
1498: {
1499: $classReflection = $this->getClassReflection();
1500: if ($classReflection === null) {
1501: return TrinaryLogic::createMaybe();
1502: }
1503:
1504: foreach (self::EXTRA_OFFSET_CLASSES as $extraOffsetClass) {
1505: if ($classReflection->is($extraOffsetClass)) {
1506: return TrinaryLogic::createYes();
1507: }
1508: }
1509:
1510: if ($classReflection->isInterface()) {
1511: return TrinaryLogic::createMaybe();
1512: }
1513:
1514: if ($classReflection->isFinal()) {
1515: return TrinaryLogic::createNo();
1516: }
1517:
1518: return TrinaryLogic::createMaybe();
1519: }
1520:
1521: public function isOffsetAccessible(): TrinaryLogic
1522: {
1523: return $this->isInstanceOf(ArrayAccess::class)->or(
1524: $this->isExtraOffsetAccessibleClass(),
1525: );
1526: }
1527:
1528: public function isOffsetAccessLegal(): TrinaryLogic
1529: {
1530: return $this->isOffsetAccessible();
1531: }
1532:
1533: public function hasOffsetValueType(Type $offsetType): TrinaryLogic
1534: {
1535: if ($this->isInstanceOf(ArrayAccess::class)->yes()) {
1536: $acceptedOffsetType = RecursionGuard::run($this, function (): Type {
1537: $parameters = $this->getMethod('offsetSet', new OutOfClassScope())->getOnlyVariant()->getParameters();
1538: if (count($parameters) < 2) {
1539: throw new ShouldNotHappenException(sprintf(
1540: 'Method %s::%s() has less than 2 parameters.',
1541: $this->className,
1542: 'offsetSet',
1543: ));
1544: }
1545:
1546: $offsetParameter = $parameters[0];
1547:
1548: return $offsetParameter->getType();
1549: });
1550:
1551: if ($acceptedOffsetType->isSuperTypeOf($offsetType)->no()) {
1552: return TrinaryLogic::createNo();
1553: }
1554:
1555: return TrinaryLogic::createMaybe();
1556: }
1557:
1558: return $this->isExtraOffsetAccessibleClass()
1559: ->and(TrinaryLogic::createMaybe());
1560: }
1561:
1562: public function getOffsetValueType(Type $offsetType): Type
1563: {
1564: if ($this->isInstanceOf(ArrayAccess::class)->yes()) {
1565: return RecursionGuard::run($this, fn (): Type => $this->getMethod('offsetGet', new OutOfClassScope())->getOnlyVariant()->getReturnType());
1566: }
1567:
1568: if (!$this->isExtraOffsetAccessibleClass()->no()) {
1569: return new MixedType();
1570: }
1571:
1572: return new ErrorType();
1573: }
1574:
1575: public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type
1576: {
1577: if ($this->isOffsetAccessible()->no()) {
1578: return new ErrorType();
1579: }
1580:
1581: if ($this->isInstanceOf(ArrayAccess::class)->yes()) {
1582: $acceptedValueType = new NeverType();
1583: $acceptedOffsetType = RecursionGuard::run($this, function () use (&$acceptedValueType): Type {
1584: $parameters = $this->getMethod('offsetSet', new OutOfClassScope())->getOnlyVariant()->getParameters();
1585: if (count($parameters) < 2) {
1586: throw new ShouldNotHappenException(sprintf(
1587: 'Method %s::%s() has less than 2 parameters.',
1588: $this->className,
1589: 'offsetSet',
1590: ));
1591: }
1592:
1593: $offsetParameter = $parameters[0];
1594: $acceptedValueType = $parameters[1]->getType();
1595:
1596: return $offsetParameter->getType();
1597: });
1598:
1599: if ($offsetType === null) {
1600: $offsetType = new NullType();
1601: }
1602:
1603: if (
1604: (!$offsetType instanceof MixedType && !$acceptedOffsetType->isSuperTypeOf($offsetType)->yes())
1605: || (!$valueType instanceof MixedType && !$acceptedValueType->isSuperTypeOf($valueType)->yes())
1606: ) {
1607: return new ErrorType();
1608: }
1609: }
1610:
1611: // in the future we may return intersection of $this and OffsetAccessibleType()
1612: return $this;
1613: }
1614:
1615: public function setExistingOffsetValueType(Type $offsetType, Type $valueType): Type
1616: {
1617: if ($this->isOffsetAccessible()->no()) {
1618: return new ErrorType();
1619: }
1620:
1621: return $this;
1622: }
1623:
1624: public function unsetOffset(Type $offsetType): Type
1625: {
1626: if ($this->isOffsetAccessible()->no()) {
1627: return new ErrorType();
1628: }
1629:
1630: return $this;
1631: }
1632:
1633: public function getEnumCases(): array
1634: {
1635: $classReflection = $this->getClassReflection();
1636: if ($classReflection === null) {
1637: return [];
1638: }
1639:
1640: if (!$classReflection->isEnum()) {
1641: return [];
1642: }
1643:
1644: $cacheKey = $this->describeCache();
1645: self::touchDescriptionCacheKey($cacheKey);
1646: if (array_key_exists($cacheKey, self::$enumCases)) {
1647: return self::$enumCases[$cacheKey];
1648: }
1649:
1650: $className = $classReflection->getName();
1651:
1652: if ($this->subtractedType !== null) {
1653: $subtractedEnumCaseNames = [];
1654:
1655: foreach ($this->subtractedType->getEnumCases() as $subtractedCase) {
1656: $subtractedEnumCaseNames[$subtractedCase->getEnumCaseName()] = true;
1657: }
1658:
1659: $cases = [];
1660: foreach ($classReflection->getEnumCases() as $enumCase) {
1661: if (array_key_exists($enumCase->getName(), $subtractedEnumCaseNames)) {
1662: continue;
1663: }
1664: $cases[] = new EnumCaseObjectType($className, $enumCase->getName(), $classReflection);
1665: }
1666: } else {
1667: $cases = [];
1668: foreach ($classReflection->getEnumCases() as $enumCase) {
1669: $cases[] = new EnumCaseObjectType($className, $enumCase->getName(), $classReflection);
1670: }
1671: }
1672:
1673: return self::$enumCases[$cacheKey] = $cases;
1674: }
1675:
1676: public function getEnumCaseObject(): ?EnumCaseObjectType
1677: {
1678: $cases = $this->getEnumCases();
1679:
1680: if (count($cases) === 1) {
1681: return $cases[0];
1682: }
1683:
1684: return null;
1685: }
1686:
1687: public function isCallable(): TrinaryLogic
1688: {
1689: $parametersAcceptors = RecursionGuard::run($this, fn () => $this->findCallableParametersAcceptors());
1690: if ($parametersAcceptors === null) {
1691: return TrinaryLogic::createNo();
1692: }
1693: if ($parametersAcceptors instanceof ErrorType) {
1694: return TrinaryLogic::createNo();
1695: }
1696:
1697: if (
1698: count($parametersAcceptors) === 1
1699: && $parametersAcceptors[0] instanceof TrivialParametersAcceptor
1700: ) {
1701: return TrinaryLogic::createMaybe();
1702: }
1703:
1704: return TrinaryLogic::createYes();
1705: }
1706:
1707: public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array
1708: {
1709: if ($this->className === Closure::class) {
1710: return [new TrivialParametersAcceptor('Closure')];
1711: }
1712: $parametersAcceptors = $this->findCallableParametersAcceptors();
1713: if ($parametersAcceptors === null) {
1714: throw new ShouldNotHappenException();
1715: }
1716:
1717: return $parametersAcceptors;
1718: }
1719:
1720: /**
1721: * @return list<CallableParametersAcceptor>|null
1722: */
1723: private function findCallableParametersAcceptors(): ?array
1724: {
1725: $classReflection = $this->getClassReflection();
1726: if ($classReflection === null) {
1727: return [new TrivialParametersAcceptor()];
1728: }
1729:
1730: if ($classReflection->hasNativeMethod('__invoke')) {
1731: $method = $this->getMethod('__invoke', new OutOfClassScope());
1732: return FunctionCallableVariant::createFromVariants(
1733: $method,
1734: $method->getVariants(),
1735: );
1736: }
1737:
1738: if (!$classReflection->isFinalByKeyword()) {
1739: return [new TrivialParametersAcceptor()];
1740: }
1741:
1742: return null;
1743: }
1744:
1745: public function isCloneable(): TrinaryLogic
1746: {
1747: return TrinaryLogic::createYes();
1748: }
1749:
1750: public function isInstanceOf(string $className): TrinaryLogic
1751: {
1752: $classReflection = $this->getClassReflection();
1753: if ($classReflection === null) {
1754: return TrinaryLogic::createMaybe();
1755: }
1756:
1757: if ($classReflection->is($className)) {
1758: return TrinaryLogic::createYes();
1759: }
1760:
1761: $reflectionProvider = ReflectionProviderStaticAccessor::getInstance();
1762: if ($reflectionProvider->hasClass($className)) {
1763: $thatClassReflection = $reflectionProvider->getClass($className);
1764: if ($thatClassReflection->isFinal()) {
1765: return TrinaryLogic::createNo();
1766: }
1767: }
1768:
1769: if ($classReflection->isInterface()) {
1770: return TrinaryLogic::createMaybe();
1771: }
1772:
1773: return TrinaryLogic::createNo();
1774: }
1775:
1776: public function subtract(Type $type): Type
1777: {
1778: if ($this->subtractedType === null) {
1779: return $this->changeSubtractedType($type);
1780: }
1781:
1782: // A sealed hierarchy rebuilds its subtraction from the flattened parts
1783: // below, so normalising the union of the old and the new subtracted type
1784: // first is wasted work - and quadratic when a removal peels the subtypes
1785: // off one at a time, as removing a whole enum from its own type does.
1786: // Only the path that keeps the subtracted type as it is needs the union.
1787: $matched = $this->matchAllowedSubTypes(array_merge(
1788: TypeUtils::flattenTypes($this->subtractedType),
1789: TypeUtils::flattenTypes($type),
1790: ));
1791: if ($matched !== null) {
1792: return $matched;
1793: }
1794:
1795: return $this->changeSubtractedType(TypeCombinator::union($this->subtractedType, $type));
1796: }
1797:
1798: public function getTypeWithoutSubtractedType(): Type
1799: {
1800: return $this->changeSubtractedType(null);
1801: }
1802:
1803: /**
1804: * Drops the "this is exactly this class" flavour that ClassReflection::asFinal()
1805: * puts on the type of a `new Foo()` expression.
1806: */
1807: public function withoutFinalByKeywordOverride(): Type
1808: {
1809: if ($this->classReflection === null || !$this->classReflection->hasFinalByKeywordOverride()) {
1810: return $this;
1811: }
1812:
1813: return new self(
1814: $this->className,
1815: $this->subtractedType,
1816: $this->classReflection->withoutFinalByKeywordOverride(),
1817: );
1818: }
1819:
1820: public function changeSubtractedType(?Type $subtractedType): Type
1821: {
1822: if ($subtractedType !== null) {
1823: $matched = $this->matchAllowedSubTypes(TypeUtils::flattenTypes($subtractedType));
1824: if ($matched !== null) {
1825: return $matched;
1826: }
1827: }
1828:
1829: if ($this->subtractedType === null && $subtractedType === null) {
1830: return $this;
1831: }
1832:
1833: return new self($this->className, $subtractedType);
1834: }
1835:
1836: /**
1837: * Rebuilds the type from the subtracted members of its sealed hierarchy.
1838: *
1839: * Returns null when the class has no allowed subtypes, or when a subtracted
1840: * type is not one of them - the caller then keeps the subtracted type whole.
1841: *
1842: * @param array<Type> $subtractedTypes flattened, may repeat
1843: */
1844: private function matchAllowedSubTypes(array $subtractedTypes): ?Type
1845: {
1846: $classReflection = $this->getClassReflection();
1847: $allowedSubTypes = $classReflection !== null ? $classReflection->getAllowedSubTypes() : null;
1848: if ($allowedSubTypes === null) {
1849: return null;
1850: }
1851:
1852: $allowedSubTypesCount = count($allowedSubTypes);
1853: $subtractedSubTypes = [];
1854:
1855: // Enum cases are finite values: FiniteTypeSet keys them by value identity, so
1856: // a subtracted case is a lookup instead of an equals() sweep over every
1857: // allowed case - quadratic in the enum size. Allowed subtypes it cannot key
1858: // (a sealed class hierarchy) still take the sweep.
1859: $allowedSet = FiniteTypeSet::create(array_values($allowedSubTypes));
1860: $keyedAllowedSubTypes = $allowedSet !== null ? $allowedSet->getMembers() : [];
1861: $otherAllowedSubTypes = $allowedSet !== null ? $allowedSet->getOthers() : array_values($allowedSubTypes);
1862:
1863: foreach ($subtractedTypes as $subType) {
1864: $key = FiniteTypeSet::key($subType);
1865: if ($key !== null) {
1866: if (!array_key_exists($key, $keyedAllowedSubTypes) || !$subType->equals($keyedAllowedSubTypes[$key])) {
1867: return null;
1868: }
1869:
1870: $subtractedSubTypes[] = $subType;
1871: unset($keyedAllowedSubTypes[$key]);
1872: continue;
1873: }
1874:
1875: foreach ($otherAllowedSubTypes as $i => $allowedSubType) {
1876: if ($subType->equals($allowedSubType)) {
1877: // An allowed subtype is dropped as it matches, so no two matches
1878: // can be the same one and the matches need no keying.
1879: $subtractedSubTypes[] = $subType;
1880: unset($otherAllowedSubTypes[$i]);
1881: continue 2;
1882: }
1883: }
1884:
1885: return null;
1886: }
1887:
1888: $remainingAllowedSubTypes = array_merge(array_values($keyedAllowedSubTypes), array_values($otherAllowedSubTypes));
1889: if (count($remainingAllowedSubTypes) === 1) {
1890: return $remainingAllowedSubTypes[0];
1891: }
1892:
1893: $subtractedSubTypesCount = count($subtractedSubTypes);
1894: if ($subtractedSubTypesCount === $allowedSubTypesCount) {
1895: return new NeverType();
1896: }
1897:
1898: if ($subtractedSubTypesCount === 0) {
1899: return new self($this->className);
1900: }
1901:
1902: if ($subtractedSubTypesCount === 1) {
1903: return new self($this->className, $subtractedSubTypes[0]);
1904: }
1905:
1906: return new self($this->className, new UnionType($subtractedSubTypes));
1907: }
1908:
1909: public function getSubtractedType(): ?Type
1910: {
1911: return $this->subtractedType;
1912: }
1913:
1914: public function traverse(callable $cb): Type
1915: {
1916: $subtractedType = $this->subtractedType !== null ? $cb($this->subtractedType) : null;
1917:
1918: if ($subtractedType !== $this->subtractedType) {
1919: return new self(
1920: $this->className,
1921: $subtractedType,
1922: );
1923: }
1924:
1925: return $this;
1926: }
1927:
1928: public function traverseSimultaneously(Type $right, callable $cb): Type
1929: {
1930: if ($this->subtractedType === null) {
1931: return $this;
1932: }
1933:
1934: return new self($this->className);
1935: }
1936:
1937: public function getNakedClassReflection(): ?ClassReflection
1938: {
1939: $classReflection = $this->resolvedClassReflection();
1940: if ($classReflection !== null) {
1941: return $classReflection;
1942: }
1943:
1944: $reflectionProvider = ReflectionProviderStaticAccessor::getInstance();
1945: if (!$reflectionProvider->hasClass($this->className)) {
1946: return null;
1947: }
1948:
1949: return $reflectionProvider->getClass($this->className);
1950: }
1951:
1952: public function getClassReflection(): ?ClassReflection
1953: {
1954: $classReflection = $this->resolvedClassReflection();
1955: if ($classReflection === null) {
1956: $reflectionProvider = ReflectionProviderStaticAccessor::getInstance();
1957: if (!$reflectionProvider->hasClass($this->className)) {
1958: return null;
1959: }
1960:
1961: $classReflection = $this->lazyClassReflection = $reflectionProvider->getClass($this->className);
1962: }
1963:
1964: if ($classReflection->isGeneric()) {
1965: return $classReflection->withTypes(array_values($classReflection->getTemplateTypeMap()->map(static fn (): Type => new ErrorType())->getTypes()));
1966: }
1967:
1968: return $classReflection;
1969: }
1970:
1971: /** The reflection already known, from either source — never triggers a fetch. */
1972: private function resolvedClassReflection(): ?ClassReflection
1973: {
1974: return $this->classReflection ?? $this->lazyClassReflection;
1975: }
1976:
1977: public function getAncestorWithClassName(string $className): ?self
1978: {
1979: if ($this->className === $className) {
1980: return $this;
1981: }
1982:
1983: $resolvedClassReflection = $this->resolvedClassReflection();
1984: if ($resolvedClassReflection !== null && $className === $resolvedClassReflection->getName()) {
1985: return $this;
1986: }
1987:
1988: if (array_key_exists($className, $this->currentAncestors)) {
1989: return $this->currentAncestors[$className];
1990: }
1991:
1992: $description = $this->describeCache();
1993: self::touchDescriptionCacheKey($description);
1994: if (
1995: array_key_exists($description, self::$ancestors)
1996: && array_key_exists($className, self::$ancestors[$description])
1997: ) {
1998: return self::$ancestors[$description][$className];
1999: }
2000:
2001: $reflectionProvider = ReflectionProviderStaticAccessor::getInstance();
2002: if (!$reflectionProvider->hasClass($className)) {
2003: return self::$ancestors[$description][$className] = $this->currentAncestors[$className] = null;
2004: }
2005: $theirReflection = $reflectionProvider->getClass($className);
2006:
2007: $thisReflection = $this->getClassReflection();
2008: if ($thisReflection === null) {
2009: return self::$ancestors[$description][$className] = $this->currentAncestors[$className] = null;
2010: }
2011: if ($theirReflection->getName() === $thisReflection->getName()) {
2012: return self::$ancestors[$description][$className] = $this->currentAncestors[$className] = $this;
2013: }
2014:
2015: foreach ($this->getInterfaces() as $interface) {
2016: $ancestor = $interface->getAncestorWithClassName($className);
2017: if ($ancestor !== null) {
2018: return self::$ancestors[$description][$className] = $this->currentAncestors[$className] = $ancestor;
2019: }
2020: }
2021:
2022: $parent = $this->getParent();
2023: if ($parent !== null) {
2024: $ancestor = $parent->getAncestorWithClassName($className);
2025: if ($ancestor !== null) {
2026: return self::$ancestors[$description][$className] = $this->currentAncestors[$className] = $ancestor;
2027: }
2028: }
2029:
2030: return self::$ancestors[$description][$className] = $this->currentAncestors[$className] = null;
2031: }
2032:
2033: private function getParent(): ?ObjectType
2034: {
2035: if ($this->cachedParent !== null) {
2036: return $this->cachedParent;
2037: }
2038: $thisReflection = $this->getClassReflection();
2039: if ($thisReflection === null) {
2040: return null;
2041: }
2042:
2043: $parentReflection = $thisReflection->getParentClass();
2044: if ($parentReflection === null) {
2045: return null;
2046: }
2047:
2048: return $this->cachedParent = $parentReflection->getObjectType();
2049: }
2050:
2051: /** @return ObjectType[] */
2052: private function getInterfaces(): array
2053: {
2054: if ($this->cachedInterfaces !== null) {
2055: return $this->cachedInterfaces;
2056: }
2057: $thisReflection = $this->getClassReflection();
2058: if ($thisReflection === null) {
2059: return $this->cachedInterfaces = [];
2060: }
2061:
2062: return $this->cachedInterfaces = array_map(static fn (ClassReflection $interfaceReflection): self => $interfaceReflection->getObjectType(), $thisReflection->getInterfaces());
2063: }
2064:
2065: public function tryRemove(Type $typeToRemove): ?Type
2066: {
2067: if ($typeToRemove instanceof ObjectType) {
2068: foreach (UnionType::EQUAL_UNION_CLASSES as $baseClass => $classes) {
2069: if ($this->getClassName() !== $baseClass) {
2070: continue;
2071: }
2072:
2073: foreach ($classes as $index => $class) {
2074: if ($typeToRemove->getClassName() === $class) {
2075: unset($classes[$index]);
2076:
2077: return TypeCombinator::union(
2078: ...array_map(static fn (string $objectClass): Type => new ObjectType($objectClass), $classes),
2079: );
2080: }
2081: }
2082: }
2083: }
2084:
2085: if ($this->isSuperTypeOf($typeToRemove)->yes()) {
2086: return $this->subtract($typeToRemove);
2087: }
2088:
2089: $classReflection = $this->getClassReflection();
2090: if ($typeToRemove instanceof UnionType && $classReflection !== null && $classReflection->getAllowedSubTypes() !== null) {
2091: // A sealed hierarchy subtracts by set difference, so the members this
2092: // type no longer holds (already subtracted) are no-ops and the rest come
2093: // off in one subtraction - the same result as removing them one at a
2094: // time, without rebuilding the subtracted union once per member.
2095: $membersToRemove = [];
2096: foreach ($typeToRemove->getTypes() as $member) {
2097: $isSuperTypeOfMember = $this->isSuperTypeOf($member);
2098: if ($isSuperTypeOfMember->yes()) {
2099: $membersToRemove[] = $member;
2100: continue;
2101: }
2102: if ($isSuperTypeOfMember->maybe()) {
2103: return null;
2104: }
2105: }
2106:
2107: if ($membersToRemove === []) {
2108: return $this;
2109: }
2110:
2111: return $this->subtract(count($membersToRemove) === 1 ? $membersToRemove[0] : new UnionType($membersToRemove));
2112: }
2113:
2114: return null;
2115: }
2116:
2117: public function getFiniteTypes(): array
2118: {
2119: return $this->getEnumCases();
2120: }
2121:
2122: public function exponentiate(Type $exponent): Type
2123: {
2124: $object = new ObjectWithoutClassType();
2125: if (!$exponent instanceof NeverType && !$object->isSuperTypeOf($this)->no() && !$object->isSuperTypeOf($exponent)->no()) {
2126: return TypeCombinator::union($this, $exponent);
2127: }
2128: return new ErrorType();
2129: }
2130:
2131: public function toPhpDocNode(): TypeNode
2132: {
2133: return new IdentifierTypeNode($this->getClassName());
2134: }
2135:
2136: public function hasTemplateOrLateResolvableType(): bool
2137: {
2138: if ($this->subtractedType === null) {
2139: return false;
2140: }
2141:
2142: return $this->subtractedType->hasTemplateOrLateResolvableType();
2143: }
2144:
2145: }
2146: