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