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