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