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