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