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