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