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 isDecimalIntegerString(): TrinaryLogic
1307: {
1308: return TrinaryLogic::createNo();
1309: }
1310:
1311: public function isNonEmptyString(): TrinaryLogic
1312: {
1313: return TrinaryLogic::createNo();
1314: }
1315:
1316: public function isNonFalsyString(): TrinaryLogic
1317: {
1318: return TrinaryLogic::createNo();
1319: }
1320:
1321: public function isLiteralString(): TrinaryLogic
1322: {
1323: return TrinaryLogic::createNo();
1324: }
1325:
1326: public function isLowercaseString(): TrinaryLogic
1327: {
1328: return TrinaryLogic::createNo();
1329: }
1330:
1331: public function isClassString(): TrinaryLogic
1332: {
1333: return TrinaryLogic::createNo();
1334: }
1335:
1336: public function isUppercaseString(): TrinaryLogic
1337: {
1338: return TrinaryLogic::createNo();
1339: }
1340:
1341: public function getClassStringObjectType(): Type
1342: {
1343: return new ErrorType();
1344: }
1345:
1346: public function getObjectTypeOrClassStringObjectType(): Type
1347: {
1348: return $this;
1349: }
1350:
1351: public function isVoid(): TrinaryLogic
1352: {
1353: return TrinaryLogic::createNo();
1354: }
1355:
1356: public function isScalar(): TrinaryLogic
1357: {
1358: return TrinaryLogic::createNo();
1359: }
1360:
1361: public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType
1362: {
1363: if ($type->isTrue()->yes()) {
1364: return new ConstantBooleanType(true);
1365: }
1366:
1367: return $type->isFalse()->yes()
1368: ? new ConstantBooleanType(false)
1369: : new BooleanType();
1370: }
1371:
1372: private function isExtraOffsetAccessibleClass(): TrinaryLogic
1373: {
1374: $classReflection = $this->getClassReflection();
1375: if ($classReflection === null) {
1376: return TrinaryLogic::createMaybe();
1377: }
1378:
1379: foreach (self::EXTRA_OFFSET_CLASSES as $extraOffsetClass) {
1380: if ($classReflection->is($extraOffsetClass)) {
1381: return TrinaryLogic::createYes();
1382: }
1383: }
1384:
1385: if ($classReflection->isInterface()) {
1386: return TrinaryLogic::createMaybe();
1387: }
1388:
1389: if ($classReflection->isFinal()) {
1390: return TrinaryLogic::createNo();
1391: }
1392:
1393: return TrinaryLogic::createMaybe();
1394: }
1395:
1396: public function isOffsetAccessible(): TrinaryLogic
1397: {
1398: return $this->isInstanceOf(ArrayAccess::class)->or(
1399: $this->isExtraOffsetAccessibleClass(),
1400: );
1401: }
1402:
1403: public function isOffsetAccessLegal(): TrinaryLogic
1404: {
1405: return $this->isOffsetAccessible();
1406: }
1407:
1408: public function hasOffsetValueType(Type $offsetType): TrinaryLogic
1409: {
1410: if ($this->isInstanceOf(ArrayAccess::class)->yes()) {
1411: $acceptedOffsetType = RecursionGuard::run($this, function (): Type {
1412: $parameters = $this->getMethod('offsetSet', new OutOfClassScope())->getOnlyVariant()->getParameters();
1413: if (count($parameters) < 2) {
1414: throw new ShouldNotHappenException(sprintf(
1415: 'Method %s::%s() has less than 2 parameters.',
1416: $this->className,
1417: 'offsetSet',
1418: ));
1419: }
1420:
1421: $offsetParameter = $parameters[0];
1422:
1423: return $offsetParameter->getType();
1424: });
1425:
1426: if ($acceptedOffsetType->isSuperTypeOf($offsetType)->no()) {
1427: return TrinaryLogic::createNo();
1428: }
1429:
1430: return TrinaryLogic::createMaybe();
1431: }
1432:
1433: return $this->isExtraOffsetAccessibleClass()
1434: ->and(TrinaryLogic::createMaybe());
1435: }
1436:
1437: public function getOffsetValueType(Type $offsetType): Type
1438: {
1439: if ($this->isInstanceOf(ArrayAccess::class)->yes()) {
1440: return RecursionGuard::run($this, fn (): Type => $this->getMethod('offsetGet', new OutOfClassScope())->getOnlyVariant()->getReturnType());
1441: }
1442:
1443: if (!$this->isExtraOffsetAccessibleClass()->no()) {
1444: return new MixedType();
1445: }
1446:
1447: return new ErrorType();
1448: }
1449:
1450: public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type
1451: {
1452: if ($this->isOffsetAccessible()->no()) {
1453: return new ErrorType();
1454: }
1455:
1456: if ($this->isInstanceOf(ArrayAccess::class)->yes()) {
1457: $acceptedValueType = new NeverType();
1458: $acceptedOffsetType = RecursionGuard::run($this, function () use (&$acceptedValueType): Type {
1459: $parameters = $this->getMethod('offsetSet', new OutOfClassScope())->getOnlyVariant()->getParameters();
1460: if (count($parameters) < 2) {
1461: throw new ShouldNotHappenException(sprintf(
1462: 'Method %s::%s() has less than 2 parameters.',
1463: $this->className,
1464: 'offsetSet',
1465: ));
1466: }
1467:
1468: $offsetParameter = $parameters[0];
1469: $acceptedValueType = $parameters[1]->getType();
1470:
1471: return $offsetParameter->getType();
1472: });
1473:
1474: if ($offsetType === null) {
1475: $offsetType = new NullType();
1476: }
1477:
1478: if (
1479: (!$offsetType instanceof MixedType && !$acceptedOffsetType->isSuperTypeOf($offsetType)->yes())
1480: || (!$valueType instanceof MixedType && !$acceptedValueType->isSuperTypeOf($valueType)->yes())
1481: ) {
1482: return new ErrorType();
1483: }
1484: }
1485:
1486: // in the future we may return intersection of $this and OffsetAccessibleType()
1487: return $this;
1488: }
1489:
1490: public function setExistingOffsetValueType(Type $offsetType, Type $valueType): Type
1491: {
1492: if ($this->isOffsetAccessible()->no()) {
1493: return new ErrorType();
1494: }
1495:
1496: return $this;
1497: }
1498:
1499: public function unsetOffset(Type $offsetType): Type
1500: {
1501: if ($this->isOffsetAccessible()->no()) {
1502: return new ErrorType();
1503: }
1504:
1505: return $this;
1506: }
1507:
1508: public function getEnumCases(): array
1509: {
1510: $classReflection = $this->getClassReflection();
1511: if ($classReflection === null) {
1512: return [];
1513: }
1514:
1515: if (!$classReflection->isEnum()) {
1516: return [];
1517: }
1518:
1519: $cacheKey = $this->describeCache();
1520: if (array_key_exists($cacheKey, self::$enumCases)) {
1521: return self::$enumCases[$cacheKey];
1522: }
1523:
1524: $className = $classReflection->getName();
1525:
1526: if ($this->subtractedType !== null) {
1527: $subtractedEnumCaseNames = [];
1528:
1529: foreach ($this->subtractedType->getEnumCases() as $subtractedCase) {
1530: $subtractedEnumCaseNames[$subtractedCase->getEnumCaseName()] = true;
1531: }
1532:
1533: $cases = [];
1534: foreach ($classReflection->getEnumCases() as $enumCase) {
1535: if (array_key_exists($enumCase->getName(), $subtractedEnumCaseNames)) {
1536: continue;
1537: }
1538: $cases[] = new EnumCaseObjectType($className, $enumCase->getName(), $classReflection);
1539: }
1540: } else {
1541: $cases = [];
1542: foreach ($classReflection->getEnumCases() as $enumCase) {
1543: $cases[] = new EnumCaseObjectType($className, $enumCase->getName(), $classReflection);
1544: }
1545: }
1546:
1547: return self::$enumCases[$cacheKey] = $cases;
1548: }
1549:
1550: public function getEnumCaseObject(): ?EnumCaseObjectType
1551: {
1552: $cases = $this->getEnumCases();
1553:
1554: if (count($cases) === 1) {
1555: return $cases[0];
1556: }
1557:
1558: return null;
1559: }
1560:
1561: public function isCallable(): TrinaryLogic
1562: {
1563: $parametersAcceptors = RecursionGuard::run($this, fn () => $this->findCallableParametersAcceptors());
1564: if ($parametersAcceptors === null) {
1565: return TrinaryLogic::createNo();
1566: }
1567: if ($parametersAcceptors instanceof ErrorType) {
1568: return TrinaryLogic::createNo();
1569: }
1570:
1571: if (
1572: count($parametersAcceptors) === 1
1573: && $parametersAcceptors[0] instanceof TrivialParametersAcceptor
1574: ) {
1575: return TrinaryLogic::createMaybe();
1576: }
1577:
1578: return TrinaryLogic::createYes();
1579: }
1580:
1581: public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array
1582: {
1583: if ($this->className === Closure::class) {
1584: return [new TrivialParametersAcceptor('Closure')];
1585: }
1586: $parametersAcceptors = $this->findCallableParametersAcceptors();
1587: if ($parametersAcceptors === null) {
1588: throw new ShouldNotHappenException();
1589: }
1590:
1591: return $parametersAcceptors;
1592: }
1593:
1594: /**
1595: * @return list<CallableParametersAcceptor>|null
1596: */
1597: private function findCallableParametersAcceptors(): ?array
1598: {
1599: $classReflection = $this->getClassReflection();
1600: if ($classReflection === null) {
1601: return [new TrivialParametersAcceptor()];
1602: }
1603:
1604: if ($classReflection->hasNativeMethod('__invoke')) {
1605: $method = $this->getMethod('__invoke', new OutOfClassScope());
1606: return FunctionCallableVariant::createFromVariants(
1607: $method,
1608: $method->getVariants(),
1609: );
1610: }
1611:
1612: if (!$classReflection->isFinalByKeyword()) {
1613: return [new TrivialParametersAcceptor()];
1614: }
1615:
1616: return null;
1617: }
1618:
1619: public function isCloneable(): TrinaryLogic
1620: {
1621: return TrinaryLogic::createYes();
1622: }
1623:
1624: public function isInstanceOf(string $className): TrinaryLogic
1625: {
1626: $classReflection = $this->getClassReflection();
1627: if ($classReflection === null) {
1628: return TrinaryLogic::createMaybe();
1629: }
1630:
1631: if ($classReflection->is($className)) {
1632: return TrinaryLogic::createYes();
1633: }
1634:
1635: $reflectionProvider = ReflectionProviderStaticAccessor::getInstance();
1636: if ($reflectionProvider->hasClass($className)) {
1637: $thatClassReflection = $reflectionProvider->getClass($className);
1638: if ($thatClassReflection->isFinal()) {
1639: return TrinaryLogic::createNo();
1640: }
1641: }
1642:
1643: if ($classReflection->isInterface()) {
1644: return TrinaryLogic::createMaybe();
1645: }
1646:
1647: return TrinaryLogic::createNo();
1648: }
1649:
1650: public function subtract(Type $type): Type
1651: {
1652: if ($this->subtractedType !== null) {
1653: $type = TypeCombinator::union($this->subtractedType, $type);
1654: }
1655:
1656: return $this->changeSubtractedType($type);
1657: }
1658:
1659: public function getTypeWithoutSubtractedType(): Type
1660: {
1661: return $this->changeSubtractedType(null);
1662: }
1663:
1664: public function changeSubtractedType(?Type $subtractedType): Type
1665: {
1666: if ($subtractedType !== null) {
1667: $classReflection = $this->getClassReflection();
1668: $allowedSubTypes = $classReflection !== null ? $classReflection->getAllowedSubTypes() : null;
1669: if ($allowedSubTypes !== null) {
1670: $preciseVerbosity = VerbosityLevel::precise();
1671:
1672: $originalAllowedSubTypes = $allowedSubTypes;
1673: $subtractedSubTypes = [];
1674:
1675: $subtractedTypes = TypeUtils::flattenTypes($subtractedType);
1676: foreach ($subtractedTypes as $subType) {
1677: foreach ($allowedSubTypes as $key => $allowedSubType) {
1678: if ($subType->equals($allowedSubType)) {
1679: $description = $allowedSubType->describe($preciseVerbosity);
1680: $subtractedSubTypes[$description] = $subType;
1681: unset($allowedSubTypes[$key]);
1682: continue 2;
1683: }
1684: }
1685:
1686: return new self($this->className, $subtractedType);
1687: }
1688:
1689: if (count($allowedSubTypes) === 1) {
1690: return array_values($allowedSubTypes)[0];
1691: }
1692:
1693: $subtractedSubTypes = array_values($subtractedSubTypes);
1694: $subtractedSubTypesCount = count($subtractedSubTypes);
1695: if ($subtractedSubTypesCount === count($originalAllowedSubTypes)) {
1696: return new NeverType();
1697: }
1698:
1699: if ($subtractedSubTypesCount === 0) {
1700: return new self($this->className);
1701: }
1702:
1703: if ($subtractedSubTypesCount === 1) {
1704: return new self($this->className, $subtractedSubTypes[0]);
1705: }
1706:
1707: return new self($this->className, new UnionType($subtractedSubTypes));
1708: }
1709: }
1710:
1711: if ($this->subtractedType === null && $subtractedType === null) {
1712: return $this;
1713: }
1714:
1715: return new self($this->className, $subtractedType);
1716: }
1717:
1718: public function getSubtractedType(): ?Type
1719: {
1720: return $this->subtractedType;
1721: }
1722:
1723: public function traverse(callable $cb): Type
1724: {
1725: $subtractedType = $this->subtractedType !== null ? $cb($this->subtractedType) : null;
1726:
1727: if ($subtractedType !== $this->subtractedType) {
1728: return new self(
1729: $this->className,
1730: $subtractedType,
1731: );
1732: }
1733:
1734: return $this;
1735: }
1736:
1737: public function traverseSimultaneously(Type $right, callable $cb): Type
1738: {
1739: if ($this->subtractedType === null) {
1740: return $this;
1741: }
1742:
1743: return new self($this->className);
1744: }
1745:
1746: public function getNakedClassReflection(): ?ClassReflection
1747: {
1748: if ($this->classReflection !== null) {
1749: return $this->classReflection;
1750: }
1751:
1752: $reflectionProvider = ReflectionProviderStaticAccessor::getInstance();
1753: if (!$reflectionProvider->hasClass($this->className)) {
1754: return null;
1755: }
1756:
1757: return $reflectionProvider->getClass($this->className);
1758: }
1759:
1760: public function getClassReflection(): ?ClassReflection
1761: {
1762: if ($this->classReflection === null) {
1763: $reflectionProvider = ReflectionProviderStaticAccessor::getInstance();
1764: if (!$reflectionProvider->hasClass($this->className)) {
1765: return null;
1766: }
1767:
1768: $this->classReflection = $reflectionProvider->getClass($this->className);
1769: }
1770:
1771: if ($this->classReflection->isGeneric()) {
1772: return $this->classReflection->withTypes(array_values($this->classReflection->getTemplateTypeMap()->map(static fn (): Type => new ErrorType())->getTypes()));
1773: }
1774:
1775: return $this->classReflection;
1776: }
1777:
1778: public function getAncestorWithClassName(string $className): ?self
1779: {
1780: if ($this->className === $className) {
1781: return $this;
1782: }
1783:
1784: if ($this->classReflection !== null && $className === $this->classReflection->getName()) {
1785: return $this;
1786: }
1787:
1788: if (array_key_exists($className, $this->currentAncestors)) {
1789: return $this->currentAncestors[$className];
1790: }
1791:
1792: $description = $this->describeCache();
1793: if (
1794: array_key_exists($description, self::$ancestors)
1795: && array_key_exists($className, self::$ancestors[$description])
1796: ) {
1797: return self::$ancestors[$description][$className];
1798: }
1799:
1800: $reflectionProvider = ReflectionProviderStaticAccessor::getInstance();
1801: if (!$reflectionProvider->hasClass($className)) {
1802: return self::$ancestors[$description][$className] = $this->currentAncestors[$className] = null;
1803: }
1804: $theirReflection = $reflectionProvider->getClass($className);
1805:
1806: $thisReflection = $this->getClassReflection();
1807: if ($thisReflection === null) {
1808: return self::$ancestors[$description][$className] = $this->currentAncestors[$className] = null;
1809: }
1810: if ($theirReflection->getName() === $thisReflection->getName()) {
1811: return self::$ancestors[$description][$className] = $this->currentAncestors[$className] = $this;
1812: }
1813:
1814: foreach ($this->getInterfaces() as $interface) {
1815: $ancestor = $interface->getAncestorWithClassName($className);
1816: if ($ancestor !== null) {
1817: return self::$ancestors[$description][$className] = $this->currentAncestors[$className] = $ancestor;
1818: }
1819: }
1820:
1821: $parent = $this->getParent();
1822: if ($parent !== null) {
1823: $ancestor = $parent->getAncestorWithClassName($className);
1824: if ($ancestor !== null) {
1825: return self::$ancestors[$description][$className] = $this->currentAncestors[$className] = $ancestor;
1826: }
1827: }
1828:
1829: return self::$ancestors[$description][$className] = $this->currentAncestors[$className] = null;
1830: }
1831:
1832: private function getParent(): ?ObjectType
1833: {
1834: if ($this->cachedParent !== null) {
1835: return $this->cachedParent;
1836: }
1837: $thisReflection = $this->getClassReflection();
1838: if ($thisReflection === null) {
1839: return null;
1840: }
1841:
1842: $parentReflection = $thisReflection->getParentClass();
1843: if ($parentReflection === null) {
1844: return null;
1845: }
1846:
1847: return $this->cachedParent = $parentReflection->getObjectType();
1848: }
1849:
1850: /** @return ObjectType[] */
1851: private function getInterfaces(): array
1852: {
1853: if ($this->cachedInterfaces !== null) {
1854: return $this->cachedInterfaces;
1855: }
1856: $thisReflection = $this->getClassReflection();
1857: if ($thisReflection === null) {
1858: return $this->cachedInterfaces = [];
1859: }
1860:
1861: return $this->cachedInterfaces = array_map(static fn (ClassReflection $interfaceReflection): self => $interfaceReflection->getObjectType(), $thisReflection->getInterfaces());
1862: }
1863:
1864: public function tryRemove(Type $typeToRemove): ?Type
1865: {
1866: if ($typeToRemove instanceof ObjectType) {
1867: foreach (UnionType::EQUAL_UNION_CLASSES as $baseClass => $classes) {
1868: if ($this->getClassName() !== $baseClass) {
1869: continue;
1870: }
1871:
1872: foreach ($classes as $index => $class) {
1873: if ($typeToRemove->getClassName() === $class) {
1874: unset($classes[$index]);
1875:
1876: return TypeCombinator::union(
1877: ...array_map(static fn (string $objectClass): Type => new ObjectType($objectClass), $classes),
1878: );
1879: }
1880: }
1881: }
1882: }
1883:
1884: if ($this->isSuperTypeOf($typeToRemove)->yes()) {
1885: return $this->subtract($typeToRemove);
1886: }
1887:
1888: return null;
1889: }
1890:
1891: public function getFiniteTypes(): array
1892: {
1893: return $this->getEnumCases();
1894: }
1895:
1896: public function exponentiate(Type $exponent): Type
1897: {
1898: $object = new ObjectWithoutClassType();
1899: if (!$exponent instanceof NeverType && !$object->isSuperTypeOf($this)->no() && !$object->isSuperTypeOf($exponent)->no()) {
1900: return TypeCombinator::union($this, $exponent);
1901: }
1902: return new ErrorType();
1903: }
1904:
1905: public function toPhpDocNode(): TypeNode
1906: {
1907: return new IdentifierTypeNode($this->getClassName());
1908: }
1909:
1910: public function hasTemplateOrLateResolvableType(): bool
1911: {
1912: if ($this->subtractedType === null) {
1913: return false;
1914: }
1915:
1916: return $this->subtractedType->hasTemplateOrLateResolvableType();
1917: }
1918:
1919: }
1920: