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 (
756: $this->isInstanceOf('SimpleXMLElement')->yes()
757: || $this->isInstanceOf('GMP')->yes()
758: ) {
759: return new UnionType([
760: new FloatType(),
761: new IntegerType(),
762: ]);
763: }
764:
765: return new ErrorType();
766: }
767:
768: public function toAbsoluteNumber(): Type
769: {
770: return $this->toNumber()->toAbsoluteNumber();
771: }
772:
773: public function toInteger(): Type
774: {
775: if ($this->isInstanceOf('SimpleXMLElement')->yes()) {
776: return new IntegerType();
777: }
778:
779: if (in_array($this->getClassName(), ['CurlHandle', 'CurlMultiHandle'], true)) {
780: return new IntegerType();
781: }
782:
783: return new ErrorType();
784: }
785:
786: public function toFloat(): Type
787: {
788: if ($this->isInstanceOf('SimpleXMLElement')->yes()) {
789: return new FloatType();
790: }
791: return new ErrorType();
792: }
793:
794: public function toString(): Type
795: {
796: if ($this->isInstanceOf('BcMath\Number')->yes()) {
797: return new IntersectionType([
798: new StringType(),
799: new AccessoryNumericStringType(),
800: new AccessoryNonEmptyStringType(),
801: ]);
802: }
803:
804: $classReflection = $this->getClassReflection();
805: if ($classReflection === null) {
806: return new ErrorType();
807: }
808:
809: if ($classReflection->hasNativeMethod('__toString')) {
810: return $this->getMethod('__toString', new OutOfClassScope())->getOnlyVariant()->getReturnType();
811: }
812:
813: return new ErrorType();
814: }
815:
816: public function toArray(): Type
817: {
818: $classReflection = $this->getClassReflection();
819: if ($classReflection === null) {
820: return new ArrayType(new MixedType(), new MixedType());
821: }
822:
823: $reflectionProvider = ReflectionProviderStaticAccessor::getInstance();
824:
825: if (
826: !$classReflection->getNativeReflection()->isUserDefined()
827: || $classReflection->is(ArrayObject::class)
828: || UniversalObjectCratesClassReflectionExtension::isUniversalObjectCrate(
829: $reflectionProvider,
830: $classReflection,
831: )
832: ) {
833: return new ArrayType(new MixedType(), new MixedType());
834: }
835: $arrayKeys = [];
836: $arrayValues = [];
837:
838: $isFinal = $classReflection->isFinal();
839:
840: do {
841: foreach ($classReflection->getNativeReflection()->getProperties() as $nativeProperty) {
842: if ($nativeProperty->isStatic()) {
843: continue;
844: }
845:
846: $declaringClass = $reflectionProvider->getClass($nativeProperty->getDeclaringClass()->getName());
847: $property = $declaringClass->getNativeProperty($nativeProperty->getName());
848:
849: $keyName = $nativeProperty->getName();
850: if ($nativeProperty->isPrivate()) {
851: $keyName = sprintf(
852: "\0%s\0%s",
853: $declaringClass->getName(),
854: $keyName,
855: );
856: } elseif ($nativeProperty->isProtected()) {
857: $keyName = sprintf(
858: "\0*\0%s",
859: $keyName,
860: );
861: }
862:
863: $arrayKeys[] = new ConstantStringType($keyName);
864: $arrayValues[] = $property->getReadableType();
865: }
866:
867: $classReflection = $classReflection->getParentClass();
868: } while ($classReflection !== null);
869:
870: if (!$isFinal) {
871: if (count($arrayKeys) === 0 || count($arrayKeys) > 16) {
872: return new ArrayType(new MixedType(), new MixedType());
873: }
874:
875: $types = [new ArrayType(new MixedType(), new MixedType())];
876: foreach ($arrayKeys as $i => $arrayKey) {
877: $types[] = new HasOffsetValueType($arrayKey, $arrayValues[$i]);
878: }
879:
880: return new IntersectionType($types);
881: }
882:
883: return new ConstantArrayType($arrayKeys, $arrayValues);
884: }
885:
886: public function toArrayKey(): Type
887: {
888: return new ErrorType();
889: }
890:
891: public function toCoercedArgumentType(bool $strictTypes): Type
892: {
893: if (!$strictTypes) {
894: $classReflection = $this->getClassReflection();
895: if (
896: $classReflection === null
897: || !$classReflection->hasNativeMethod('__toString')
898: ) {
899: return $this;
900: }
901:
902: return TypeCombinator::union($this, $this->toString());
903: }
904:
905: return $this;
906: }
907:
908: public function toBoolean(): BooleanType
909: {
910: if (
911: $this->isInstanceOf('SimpleXMLElement')->yes()
912: || $this->isInstanceOf('BcMath\Number')->yes()
913: ) {
914: return new BooleanType();
915: }
916:
917: return new ConstantBooleanType(true);
918: }
919:
920: public function isObject(): TrinaryLogic
921: {
922: return TrinaryLogic::createYes();
923: }
924:
925: public function isEnum(): TrinaryLogic
926: {
927: $classReflection = $this->getClassReflection();
928: if ($classReflection === null) {
929: return TrinaryLogic::createMaybe();
930: }
931:
932: if (
933: $classReflection->isEnum()
934: || $classReflection->is('UnitEnum')
935: ) {
936: return TrinaryLogic::createYes();
937: }
938:
939: if (
940: $classReflection->isInterface()
941: && !$classReflection->is(Stringable::class) // enums cannot have __toString
942: && !$classReflection->is(Throwable::class) // enums cannot extend Exception/Error
943: && !$classReflection->is(DateTimeInterface::class) // userland classes cannot extend DateTimeInterface
944: ) {
945: return TrinaryLogic::createMaybe();
946: }
947:
948: return TrinaryLogic::createNo();
949: }
950:
951: public function canAccessProperties(): TrinaryLogic
952: {
953: return TrinaryLogic::createYes();
954: }
955:
956: public function canCallMethods(): TrinaryLogic
957: {
958: if (strtolower($this->className) === 'stdclass') {
959: return TrinaryLogic::createNo();
960: }
961:
962: return TrinaryLogic::createYes();
963: }
964:
965: public function hasMethod(string $methodName): TrinaryLogic
966: {
967: $classReflection = $this->getClassReflection();
968: if ($classReflection === null) {
969: return TrinaryLogic::createMaybe();
970: }
971:
972: if ($classReflection->hasMethod($methodName)) {
973: return TrinaryLogic::createYes();
974: }
975:
976: if ($classReflection->isFinal()) {
977: return TrinaryLogic::createNo();
978: }
979:
980: return TrinaryLogic::createMaybe();
981: }
982:
983: public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): ExtendedMethodReflection
984: {
985: $key = $methodName;
986: if ($scope->isInClass()) {
987: $key = sprintf('%s-%s', $key, $scope->getClassReflection()->getCacheKey());
988: }
989: return $this->methodCache[$key] ??= $this->getUnresolvedMethodPrototype($methodName, $scope)->getTransformedMethod();
990: }
991:
992: public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection
993: {
994: if (!$scope->isInClass()) {
995: $canCallMethod = 'no';
996: } else {
997: $canCallMethod = $scope->getClassReflection()->getName();
998: }
999: $description = $this->describeCache();
1000: if (isset(self::$methods[$description][$methodName][$canCallMethod])) {
1001: return self::$methods[$description][$methodName][$canCallMethod];
1002: }
1003:
1004: $nakedClassReflection = $this->getNakedClassReflection();
1005: if ($nakedClassReflection === null) {
1006: throw new ClassNotFoundException($this->className);
1007: }
1008:
1009: if (!$nakedClassReflection->hasNativeMethod($methodName)) {
1010: $nakedClassReflection = $this->getClassReflection();
1011: }
1012:
1013: if ($nakedClassReflection === null) {
1014: throw new ClassNotFoundException($this->className);
1015: }
1016:
1017: $method = $nakedClassReflection->getMethod($methodName, $scope);
1018:
1019: $ancestor = $this->getAncestorWithClassName($method->getDeclaringClass()->getName());
1020: $resolvedClassReflection = null;
1021: if ($ancestor !== null) {
1022: $resolvedClassReflection = $ancestor->getClassReflection();
1023: if ($ancestor !== $this) {
1024: $method = $ancestor->getUnresolvedMethodPrototype($methodName, $scope)->getNakedMethod();
1025: }
1026: }
1027: if ($resolvedClassReflection === null) {
1028: $resolvedClassReflection = $method->getDeclaringClass();
1029: }
1030:
1031: return self::$methods[$description][$methodName][$canCallMethod] = new CalledOnTypeUnresolvedMethodPrototypeReflection(
1032: $method,
1033: $resolvedClassReflection,
1034: true,
1035: $this,
1036: );
1037: }
1038:
1039: public function canAccessConstants(): TrinaryLogic
1040: {
1041: return TrinaryLogic::createYes();
1042: }
1043:
1044: public function hasConstant(string $constantName): TrinaryLogic
1045: {
1046: $classReflection = $this->getClassReflection();
1047: if ($classReflection === null) {
1048: return TrinaryLogic::createMaybe();
1049: }
1050:
1051: if ($classReflection->hasConstant($constantName)) {
1052: return TrinaryLogic::createYes();
1053: }
1054:
1055: if ($classReflection->isFinal()) {
1056: return TrinaryLogic::createNo();
1057: }
1058:
1059: return TrinaryLogic::createMaybe();
1060: }
1061:
1062: public function getConstant(string $constantName): ClassConstantReflection
1063: {
1064: $class = $this->getClassReflection();
1065: if ($class === null) {
1066: throw new ClassNotFoundException($this->className);
1067: }
1068:
1069: return $class->getConstant($constantName);
1070: }
1071:
1072: public function getTemplateType(string $ancestorClassName, string $templateTypeName): Type
1073: {
1074: $classReflection = $this->getClassReflection();
1075: if ($classReflection === null) {
1076: return new ErrorType();
1077: }
1078:
1079: $ancestorClassReflection = $classReflection->getAncestorWithClassName($ancestorClassName);
1080: if ($ancestorClassReflection === null) {
1081: return new ErrorType();
1082: }
1083:
1084: $activeTemplateTypeMap = $ancestorClassReflection->getPossiblyIncompleteActiveTemplateTypeMap();
1085: $type = $activeTemplateTypeMap->getType($templateTypeName);
1086: if ($type === null) {
1087: return new ErrorType();
1088: }
1089: if ($type instanceof ErrorType) {
1090: $templateTypeMap = $ancestorClassReflection->getTemplateTypeMap();
1091: $templateType = $templateTypeMap->getType($templateTypeName);
1092: if ($templateType === null) {
1093: return $type;
1094: }
1095:
1096: $bound = TemplateTypeHelper::resolveToBounds($templateType);
1097: if ($bound instanceof MixedType && $bound->isExplicitMixed()) {
1098: return new MixedType(false);
1099: }
1100:
1101: return TemplateTypeHelper::resolveToDefaults($templateType);
1102: }
1103:
1104: return $type;
1105: }
1106:
1107: public function getConstantStrings(): array
1108: {
1109: return [];
1110: }
1111:
1112: public function isIterable(): TrinaryLogic
1113: {
1114: return $this->isInstanceOf(Traversable::class);
1115: }
1116:
1117: public function isIterableAtLeastOnce(): TrinaryLogic
1118: {
1119: return $this->isInstanceOf(Traversable::class)
1120: ->and(TrinaryLogic::createMaybe());
1121: }
1122:
1123: public function getArraySize(): Type
1124: {
1125: if ($this->isInstanceOf(Countable::class)->no()) {
1126: return new ErrorType();
1127: }
1128:
1129: if ($this->hasMethod('count')->yes() === false) {
1130: return IntegerRangeType::fromInterval(0, null);
1131: }
1132:
1133: return RecursionGuard::run($this, fn (): Type => $this->getMethod('count', new OutOfClassScope())->getOnlyVariant()->getReturnType());
1134: }
1135:
1136: public function getIterableKeyType(): Type
1137: {
1138: $isTraversable = false;
1139: if ($this->isInstanceOf(IteratorAggregate::class)->yes()) {
1140: $keyType = RecursionGuard::run($this, fn (): Type => $this->getMethod('getIterator', new OutOfClassScope())->getOnlyVariant()->getReturnType()->getIterableKeyType());
1141: $isTraversable = true;
1142: if (!$keyType instanceof MixedType || $keyType->isExplicitMixed()) {
1143: return $keyType;
1144: }
1145: }
1146:
1147: $extraOffsetAccessible = $this->isExtraOffsetAccessibleClass()->yes();
1148: if (!$extraOffsetAccessible && $this->isInstanceOf(Traversable::class)->yes()) {
1149: $isTraversable = true;
1150: $tKey = $this->getTemplateType(Traversable::class, 'TKey');
1151: if (!$tKey instanceof ErrorType) {
1152: if (!$tKey instanceof MixedType || $tKey->isExplicitMixed()) {
1153: return $tKey;
1154: }
1155: }
1156: }
1157:
1158: if ($this->isInstanceOf(Iterator::class)->yes()) {
1159: return RecursionGuard::run($this, fn (): Type => $this->getMethod('key', new OutOfClassScope())->getOnlyVariant()->getReturnType());
1160: }
1161:
1162: if ($extraOffsetAccessible) {
1163: return new MixedType(true);
1164: }
1165:
1166: if ($isTraversable) {
1167: return new MixedType();
1168: }
1169:
1170: return new ErrorType();
1171: }
1172:
1173: public function getFirstIterableKeyType(): Type
1174: {
1175: return $this->getIterableKeyType();
1176: }
1177:
1178: public function getLastIterableKeyType(): Type
1179: {
1180: return $this->getIterableKeyType();
1181: }
1182:
1183: public function getIterableValueType(): Type
1184: {
1185: $isTraversable = false;
1186: if ($this->isInstanceOf(IteratorAggregate::class)->yes()) {
1187: $valueType = RecursionGuard::run($this, fn (): Type => $this->getMethod('getIterator', new OutOfClassScope())->getOnlyVariant()->getReturnType()->getIterableValueType());
1188: $isTraversable = true;
1189: if (!$valueType instanceof MixedType || $valueType->isExplicitMixed()) {
1190: return $valueType;
1191: }
1192: }
1193:
1194: $extraOffsetAccessible = $this->isExtraOffsetAccessibleClass()->yes();
1195: if (!$extraOffsetAccessible && $this->isInstanceOf(Traversable::class)->yes()) {
1196: $isTraversable = true;
1197: $tValue = $this->getTemplateType(Traversable::class, 'TValue');
1198: if (!$tValue instanceof ErrorType) {
1199: if (!$tValue instanceof MixedType || $tValue->isExplicitMixed()) {
1200: return $tValue;
1201: }
1202: }
1203: }
1204:
1205: if ($this->isInstanceOf(Iterator::class)->yes()) {
1206: return RecursionGuard::run($this, fn (): Type => $this->getMethod('current', new OutOfClassScope())->getOnlyVariant()->getReturnType());
1207: }
1208:
1209: if ($extraOffsetAccessible) {
1210: return new MixedType(true);
1211: }
1212:
1213: if ($isTraversable) {
1214: return new MixedType();
1215: }
1216:
1217: return new ErrorType();
1218: }
1219:
1220: public function getFirstIterableValueType(): Type
1221: {
1222: return $this->getIterableValueType();
1223: }
1224:
1225: public function getLastIterableValueType(): Type
1226: {
1227: return $this->getIterableValueType();
1228: }
1229:
1230: public function isNull(): TrinaryLogic
1231: {
1232: return TrinaryLogic::createNo();
1233: }
1234:
1235: public function isConstantValue(): TrinaryLogic
1236: {
1237: return TrinaryLogic::createNo();
1238: }
1239:
1240: public function isConstantScalarValue(): TrinaryLogic
1241: {
1242: return TrinaryLogic::createNo();
1243: }
1244:
1245: public function getConstantScalarTypes(): array
1246: {
1247: return [];
1248: }
1249:
1250: public function getConstantScalarValues(): array
1251: {
1252: return [];
1253: }
1254:
1255: public function isTrue(): TrinaryLogic
1256: {
1257: return TrinaryLogic::createNo();
1258: }
1259:
1260: public function isFalse(): TrinaryLogic
1261: {
1262: return TrinaryLogic::createNo();
1263: }
1264:
1265: public function isBoolean(): TrinaryLogic
1266: {
1267: return TrinaryLogic::createNo();
1268: }
1269:
1270: public function isFloat(): TrinaryLogic
1271: {
1272: return TrinaryLogic::createNo();
1273: }
1274:
1275: public function isInteger(): TrinaryLogic
1276: {
1277: return TrinaryLogic::createNo();
1278: }
1279:
1280: public function isString(): TrinaryLogic
1281: {
1282: return TrinaryLogic::createNo();
1283: }
1284:
1285: public function isNumericString(): TrinaryLogic
1286: {
1287: return TrinaryLogic::createNo();
1288: }
1289:
1290: public function isNonEmptyString(): TrinaryLogic
1291: {
1292: return TrinaryLogic::createNo();
1293: }
1294:
1295: public function isNonFalsyString(): TrinaryLogic
1296: {
1297: return TrinaryLogic::createNo();
1298: }
1299:
1300: public function isLiteralString(): TrinaryLogic
1301: {
1302: return TrinaryLogic::createNo();
1303: }
1304:
1305: public function isLowercaseString(): TrinaryLogic
1306: {
1307: return TrinaryLogic::createNo();
1308: }
1309:
1310: public function isClassString(): TrinaryLogic
1311: {
1312: return TrinaryLogic::createNo();
1313: }
1314:
1315: public function isUppercaseString(): TrinaryLogic
1316: {
1317: return TrinaryLogic::createNo();
1318: }
1319:
1320: public function getClassStringObjectType(): Type
1321: {
1322: return new ErrorType();
1323: }
1324:
1325: public function getObjectTypeOrClassStringObjectType(): Type
1326: {
1327: return $this;
1328: }
1329:
1330: public function isVoid(): TrinaryLogic
1331: {
1332: return TrinaryLogic::createNo();
1333: }
1334:
1335: public function isScalar(): TrinaryLogic
1336: {
1337: return TrinaryLogic::createNo();
1338: }
1339:
1340: public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType
1341: {
1342: if ($type->isTrue()->yes()) {
1343: return new ConstantBooleanType(true);
1344: }
1345:
1346: return $type->isFalse()->yes()
1347: ? new ConstantBooleanType(false)
1348: : new BooleanType();
1349: }
1350:
1351: private function isExtraOffsetAccessibleClass(): TrinaryLogic
1352: {
1353: $classReflection = $this->getClassReflection();
1354: if ($classReflection === null) {
1355: return TrinaryLogic::createMaybe();
1356: }
1357:
1358: foreach (self::EXTRA_OFFSET_CLASSES as $extraOffsetClass) {
1359: if ($classReflection->is($extraOffsetClass)) {
1360: return TrinaryLogic::createYes();
1361: }
1362: }
1363:
1364: if ($classReflection->isInterface()) {
1365: return TrinaryLogic::createMaybe();
1366: }
1367:
1368: if ($classReflection->isFinal()) {
1369: return TrinaryLogic::createNo();
1370: }
1371:
1372: return TrinaryLogic::createMaybe();
1373: }
1374:
1375: public function isOffsetAccessible(): TrinaryLogic
1376: {
1377: return $this->isInstanceOf(ArrayAccess::class)->or(
1378: $this->isExtraOffsetAccessibleClass(),
1379: );
1380: }
1381:
1382: public function isOffsetAccessLegal(): TrinaryLogic
1383: {
1384: return $this->isOffsetAccessible();
1385: }
1386:
1387: public function hasOffsetValueType(Type $offsetType): TrinaryLogic
1388: {
1389: if ($this->isInstanceOf(ArrayAccess::class)->yes()) {
1390: $acceptedOffsetType = RecursionGuard::run($this, function (): Type {
1391: $parameters = $this->getMethod('offsetSet', new OutOfClassScope())->getOnlyVariant()->getParameters();
1392: if (count($parameters) < 2) {
1393: throw new ShouldNotHappenException(sprintf(
1394: 'Method %s::%s() has less than 2 parameters.',
1395: $this->className,
1396: 'offsetSet',
1397: ));
1398: }
1399:
1400: $offsetParameter = $parameters[0];
1401:
1402: return $offsetParameter->getType();
1403: });
1404:
1405: if ($acceptedOffsetType->isSuperTypeOf($offsetType)->no()) {
1406: return TrinaryLogic::createNo();
1407: }
1408:
1409: return TrinaryLogic::createMaybe();
1410: }
1411:
1412: return $this->isExtraOffsetAccessibleClass()
1413: ->and(TrinaryLogic::createMaybe());
1414: }
1415:
1416: public function getOffsetValueType(Type $offsetType): Type
1417: {
1418: if ($this->isInstanceOf(ArrayAccess::class)->yes()) {
1419: return RecursionGuard::run($this, fn (): Type => $this->getMethod('offsetGet', new OutOfClassScope())->getOnlyVariant()->getReturnType());
1420: }
1421:
1422: if (!$this->isExtraOffsetAccessibleClass()->no()) {
1423: return new MixedType();
1424: }
1425:
1426: return new ErrorType();
1427: }
1428:
1429: public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type
1430: {
1431: if ($this->isOffsetAccessible()->no()) {
1432: return new ErrorType();
1433: }
1434:
1435: if ($this->isInstanceOf(ArrayAccess::class)->yes()) {
1436: $acceptedValueType = new NeverType();
1437: $acceptedOffsetType = RecursionGuard::run($this, function () use (&$acceptedValueType): Type {
1438: $parameters = $this->getMethod('offsetSet', new OutOfClassScope())->getOnlyVariant()->getParameters();
1439: if (count($parameters) < 2) {
1440: throw new ShouldNotHappenException(sprintf(
1441: 'Method %s::%s() has less than 2 parameters.',
1442: $this->className,
1443: 'offsetSet',
1444: ));
1445: }
1446:
1447: $offsetParameter = $parameters[0];
1448: $acceptedValueType = $parameters[1]->getType();
1449:
1450: return $offsetParameter->getType();
1451: });
1452:
1453: if ($offsetType === null) {
1454: $offsetType = new NullType();
1455: }
1456:
1457: if (
1458: (!$offsetType instanceof MixedType && !$acceptedOffsetType->isSuperTypeOf($offsetType)->yes())
1459: || (!$valueType instanceof MixedType && !$acceptedValueType->isSuperTypeOf($valueType)->yes())
1460: ) {
1461: return new ErrorType();
1462: }
1463: }
1464:
1465: // in the future we may return intersection of $this and OffsetAccessibleType()
1466: return $this;
1467: }
1468:
1469: public function setExistingOffsetValueType(Type $offsetType, Type $valueType): Type
1470: {
1471: if ($this->isOffsetAccessible()->no()) {
1472: return new ErrorType();
1473: }
1474:
1475: return $this;
1476: }
1477:
1478: public function unsetOffset(Type $offsetType): Type
1479: {
1480: if ($this->isOffsetAccessible()->no()) {
1481: return new ErrorType();
1482: }
1483:
1484: return $this;
1485: }
1486:
1487: public function getEnumCases(): array
1488: {
1489: $classReflection = $this->getClassReflection();
1490: if ($classReflection === null) {
1491: return [];
1492: }
1493:
1494: if (!$classReflection->isEnum()) {
1495: return [];
1496: }
1497:
1498: $cacheKey = $this->describeCache();
1499: if (array_key_exists($cacheKey, self::$enumCases)) {
1500: return self::$enumCases[$cacheKey];
1501: }
1502:
1503: $className = $classReflection->getName();
1504:
1505: if ($this->subtractedType !== null) {
1506: $subtractedEnumCaseNames = [];
1507:
1508: foreach ($this->subtractedType->getEnumCases() as $subtractedCase) {
1509: $subtractedEnumCaseNames[$subtractedCase->getEnumCaseName()] = true;
1510: }
1511:
1512: $cases = [];
1513: foreach ($classReflection->getEnumCases() as $enumCase) {
1514: if (array_key_exists($enumCase->getName(), $subtractedEnumCaseNames)) {
1515: continue;
1516: }
1517: $cases[] = new EnumCaseObjectType($className, $enumCase->getName(), $classReflection);
1518: }
1519: } else {
1520: $cases = [];
1521: foreach ($classReflection->getEnumCases() as $enumCase) {
1522: $cases[] = new EnumCaseObjectType($className, $enumCase->getName(), $classReflection);
1523: }
1524: }
1525:
1526: return self::$enumCases[$cacheKey] = $cases;
1527: }
1528:
1529: public function getEnumCaseObject(): ?EnumCaseObjectType
1530: {
1531: $cases = $this->getEnumCases();
1532:
1533: if (count($cases) === 1) {
1534: return $cases[0];
1535: }
1536:
1537: return null;
1538: }
1539:
1540: public function isCallable(): TrinaryLogic
1541: {
1542: $parametersAcceptors = RecursionGuard::run($this, fn () => $this->findCallableParametersAcceptors());
1543: if ($parametersAcceptors === null) {
1544: return TrinaryLogic::createNo();
1545: }
1546: if ($parametersAcceptors instanceof ErrorType) {
1547: return TrinaryLogic::createNo();
1548: }
1549:
1550: if (
1551: count($parametersAcceptors) === 1
1552: && $parametersAcceptors[0] instanceof TrivialParametersAcceptor
1553: ) {
1554: return TrinaryLogic::createMaybe();
1555: }
1556:
1557: return TrinaryLogic::createYes();
1558: }
1559:
1560: public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array
1561: {
1562: if ($this->className === Closure::class) {
1563: return [new TrivialParametersAcceptor('Closure')];
1564: }
1565: $parametersAcceptors = $this->findCallableParametersAcceptors();
1566: if ($parametersAcceptors === null) {
1567: throw new ShouldNotHappenException();
1568: }
1569:
1570: return $parametersAcceptors;
1571: }
1572:
1573: /**
1574: * @return list<CallableParametersAcceptor>|null
1575: */
1576: private function findCallableParametersAcceptors(): ?array
1577: {
1578: $classReflection = $this->getClassReflection();
1579: if ($classReflection === null) {
1580: return [new TrivialParametersAcceptor()];
1581: }
1582:
1583: if ($classReflection->hasNativeMethod('__invoke')) {
1584: $method = $this->getMethod('__invoke', new OutOfClassScope());
1585: return FunctionCallableVariant::createFromVariants(
1586: $method,
1587: $method->getVariants(),
1588: );
1589: }
1590:
1591: if (!$classReflection->isFinalByKeyword()) {
1592: return [new TrivialParametersAcceptor()];
1593: }
1594:
1595: return null;
1596: }
1597:
1598: public function isCloneable(): TrinaryLogic
1599: {
1600: return TrinaryLogic::createYes();
1601: }
1602:
1603: public function isInstanceOf(string $className): TrinaryLogic
1604: {
1605: $classReflection = $this->getClassReflection();
1606: if ($classReflection === null) {
1607: return TrinaryLogic::createMaybe();
1608: }
1609:
1610: if ($classReflection->is($className)) {
1611: return TrinaryLogic::createYes();
1612: }
1613:
1614: $reflectionProvider = ReflectionProviderStaticAccessor::getInstance();
1615: if ($reflectionProvider->hasClass($className)) {
1616: $thatClassReflection = $reflectionProvider->getClass($className);
1617: if ($thatClassReflection->isFinal()) {
1618: return TrinaryLogic::createNo();
1619: }
1620: }
1621:
1622: if ($classReflection->isInterface()) {
1623: return TrinaryLogic::createMaybe();
1624: }
1625:
1626: return TrinaryLogic::createNo();
1627: }
1628:
1629: public function subtract(Type $type): Type
1630: {
1631: if ($this->subtractedType !== null) {
1632: $type = TypeCombinator::union($this->subtractedType, $type);
1633: }
1634:
1635: return $this->changeSubtractedType($type);
1636: }
1637:
1638: public function getTypeWithoutSubtractedType(): Type
1639: {
1640: return $this->changeSubtractedType(null);
1641: }
1642:
1643: public function changeSubtractedType(?Type $subtractedType): Type
1644: {
1645: if ($subtractedType !== null) {
1646: $classReflection = $this->getClassReflection();
1647: $allowedSubTypes = $classReflection !== null ? $classReflection->getAllowedSubTypes() : null;
1648: if ($allowedSubTypes !== null) {
1649: $preciseVerbosity = VerbosityLevel::precise();
1650:
1651: $originalAllowedSubTypes = $allowedSubTypes;
1652: $subtractedSubTypes = [];
1653:
1654: $subtractedTypes = TypeUtils::flattenTypes($subtractedType);
1655: foreach ($subtractedTypes as $subType) {
1656: foreach ($allowedSubTypes as $key => $allowedSubType) {
1657: if ($subType->equals($allowedSubType)) {
1658: $description = $allowedSubType->describe($preciseVerbosity);
1659: $subtractedSubTypes[$description] = $subType;
1660: unset($allowedSubTypes[$key]);
1661: continue 2;
1662: }
1663: }
1664:
1665: return new self($this->className, $subtractedType);
1666: }
1667:
1668: if (count($allowedSubTypes) === 1) {
1669: return array_values($allowedSubTypes)[0];
1670: }
1671:
1672: $subtractedSubTypes = array_values($subtractedSubTypes);
1673: $subtractedSubTypesCount = count($subtractedSubTypes);
1674: if ($subtractedSubTypesCount === count($originalAllowedSubTypes)) {
1675: return new NeverType();
1676: }
1677:
1678: if ($subtractedSubTypesCount === 0) {
1679: return new self($this->className);
1680: }
1681:
1682: if ($subtractedSubTypesCount === 1) {
1683: return new self($this->className, $subtractedSubTypes[0]);
1684: }
1685:
1686: return new self($this->className, new UnionType($subtractedSubTypes));
1687: }
1688: }
1689:
1690: if ($this->subtractedType === null && $subtractedType === null) {
1691: return $this;
1692: }
1693:
1694: return new self($this->className, $subtractedType);
1695: }
1696:
1697: public function getSubtractedType(): ?Type
1698: {
1699: return $this->subtractedType;
1700: }
1701:
1702: public function traverse(callable $cb): Type
1703: {
1704: $subtractedType = $this->subtractedType !== null ? $cb($this->subtractedType) : null;
1705:
1706: if ($subtractedType !== $this->subtractedType) {
1707: return new self(
1708: $this->className,
1709: $subtractedType,
1710: );
1711: }
1712:
1713: return $this;
1714: }
1715:
1716: public function traverseSimultaneously(Type $right, callable $cb): Type
1717: {
1718: if ($this->subtractedType === null) {
1719: return $this;
1720: }
1721:
1722: return new self($this->className);
1723: }
1724:
1725: public function getNakedClassReflection(): ?ClassReflection
1726: {
1727: if ($this->classReflection !== null) {
1728: return $this->classReflection;
1729: }
1730:
1731: $reflectionProvider = ReflectionProviderStaticAccessor::getInstance();
1732: if (!$reflectionProvider->hasClass($this->className)) {
1733: return null;
1734: }
1735:
1736: return $reflectionProvider->getClass($this->className);
1737: }
1738:
1739: public function getClassReflection(): ?ClassReflection
1740: {
1741: if ($this->classReflection === null) {
1742: $reflectionProvider = ReflectionProviderStaticAccessor::getInstance();
1743: if (!$reflectionProvider->hasClass($this->className)) {
1744: return null;
1745: }
1746:
1747: $this->classReflection = $reflectionProvider->getClass($this->className);
1748: }
1749:
1750: if ($this->classReflection->isGeneric()) {
1751: return $this->classReflection->withTypes(array_values($this->classReflection->getTemplateTypeMap()->map(static fn (): Type => new ErrorType())->getTypes()));
1752: }
1753:
1754: return $this->classReflection;
1755: }
1756:
1757: public function getAncestorWithClassName(string $className): ?self
1758: {
1759: if ($this->className === $className) {
1760: return $this;
1761: }
1762:
1763: if ($this->classReflection !== null && $className === $this->classReflection->getName()) {
1764: return $this;
1765: }
1766:
1767: if (array_key_exists($className, $this->currentAncestors)) {
1768: return $this->currentAncestors[$className];
1769: }
1770:
1771: $description = $this->describeCache();
1772: if (
1773: array_key_exists($description, self::$ancestors)
1774: && array_key_exists($className, self::$ancestors[$description])
1775: ) {
1776: return self::$ancestors[$description][$className];
1777: }
1778:
1779: $reflectionProvider = ReflectionProviderStaticAccessor::getInstance();
1780: if (!$reflectionProvider->hasClass($className)) {
1781: return self::$ancestors[$description][$className] = $this->currentAncestors[$className] = null;
1782: }
1783: $theirReflection = $reflectionProvider->getClass($className);
1784:
1785: $thisReflection = $this->getClassReflection();
1786: if ($thisReflection === null) {
1787: return self::$ancestors[$description][$className] = $this->currentAncestors[$className] = null;
1788: }
1789: if ($theirReflection->getName() === $thisReflection->getName()) {
1790: return self::$ancestors[$description][$className] = $this->currentAncestors[$className] = $this;
1791: }
1792:
1793: foreach ($this->getInterfaces() as $interface) {
1794: $ancestor = $interface->getAncestorWithClassName($className);
1795: if ($ancestor !== null) {
1796: return self::$ancestors[$description][$className] = $this->currentAncestors[$className] = $ancestor;
1797: }
1798: }
1799:
1800: $parent = $this->getParent();
1801: if ($parent !== null) {
1802: $ancestor = $parent->getAncestorWithClassName($className);
1803: if ($ancestor !== null) {
1804: return self::$ancestors[$description][$className] = $this->currentAncestors[$className] = $ancestor;
1805: }
1806: }
1807:
1808: return self::$ancestors[$description][$className] = $this->currentAncestors[$className] = null;
1809: }
1810:
1811: private function getParent(): ?ObjectType
1812: {
1813: if ($this->cachedParent !== null) {
1814: return $this->cachedParent;
1815: }
1816: $thisReflection = $this->getClassReflection();
1817: if ($thisReflection === null) {
1818: return null;
1819: }
1820:
1821: $parentReflection = $thisReflection->getParentClass();
1822: if ($parentReflection === null) {
1823: return null;
1824: }
1825:
1826: return $this->cachedParent = $parentReflection->getObjectType();
1827: }
1828:
1829: /** @return ObjectType[] */
1830: private function getInterfaces(): array
1831: {
1832: if ($this->cachedInterfaces !== null) {
1833: return $this->cachedInterfaces;
1834: }
1835: $thisReflection = $this->getClassReflection();
1836: if ($thisReflection === null) {
1837: return $this->cachedInterfaces = [];
1838: }
1839:
1840: return $this->cachedInterfaces = array_map(static fn (ClassReflection $interfaceReflection): self => $interfaceReflection->getObjectType(), $thisReflection->getInterfaces());
1841: }
1842:
1843: public function tryRemove(Type $typeToRemove): ?Type
1844: {
1845: if ($typeToRemove instanceof ObjectType) {
1846: foreach (UnionType::EQUAL_UNION_CLASSES as $baseClass => $classes) {
1847: if ($this->getClassName() !== $baseClass) {
1848: continue;
1849: }
1850:
1851: foreach ($classes as $index => $class) {
1852: if ($typeToRemove->getClassName() === $class) {
1853: unset($classes[$index]);
1854:
1855: return TypeCombinator::union(
1856: ...array_map(static fn (string $objectClass): Type => new ObjectType($objectClass), $classes),
1857: );
1858: }
1859: }
1860: }
1861: }
1862:
1863: if ($this->isSuperTypeOf($typeToRemove)->yes()) {
1864: return $this->subtract($typeToRemove);
1865: }
1866:
1867: return null;
1868: }
1869:
1870: public function getFiniteTypes(): array
1871: {
1872: return $this->getEnumCases();
1873: }
1874:
1875: public function exponentiate(Type $exponent): Type
1876: {
1877: $object = new ObjectWithoutClassType();
1878: if (!$exponent instanceof NeverType && !$object->isSuperTypeOf($this)->no() && !$object->isSuperTypeOf($exponent)->no()) {
1879: return TypeCombinator::union($this, $exponent);
1880: }
1881: return new ErrorType();
1882: }
1883:
1884: public function toPhpDocNode(): TypeNode
1885: {
1886: return new IdentifierTypeNode($this->getClassName());
1887: }
1888:
1889: public function hasTemplateOrLateResolvableType(): bool
1890: {
1891: if ($this->subtractedType === null) {
1892: return false;
1893: }
1894:
1895: return $this->subtractedType->hasTemplateOrLateResolvableType();
1896: }
1897:
1898: }
1899: