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