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