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