1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Type;
4:
5: use PHPStan\Php\PhpVersion;
6: use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
7: use PHPStan\PhpDocParser\Ast\Type\TypeNode;
8: use PHPStan\Reflection\ClassConstantReflection;
9: use PHPStan\Reflection\ClassMemberAccessAnswerer;
10: use PHPStan\Reflection\ClassReflection;
11: use PHPStan\Reflection\ExtendedMethodReflection;
12: use PHPStan\Reflection\ExtendedPropertyReflection;
13: use PHPStan\Reflection\Type\CallbackUnresolvedMethodPrototypeReflection;
14: use PHPStan\Reflection\Type\CallbackUnresolvedPropertyPrototypeReflection;
15: use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection;
16: use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection;
17: use PHPStan\TrinaryLogic;
18: use PHPStan\Type\Enum\EnumCaseObjectType;
19: use PHPStan\Type\Generic\GenericClassStringType;
20: use PHPStan\Type\Generic\GenericObjectType;
21: use PHPStan\Type\Generic\TemplateTypeHelper;
22: use PHPStan\Type\Traits\NonGeneralizableTypeTrait;
23: use PHPStan\Type\Traits\NonGenericTypeTrait;
24: use PHPStan\Type\Traits\UndecidedComparisonTypeTrait;
25: use function get_class;
26: use function sprintf;
27:
28: /** @api */
29: class StaticType implements TypeWithClassName, SubtractableType
30: {
31:
32: use NonGenericTypeTrait;
33: use UndecidedComparisonTypeTrait;
34: use NonGeneralizableTypeTrait;
35:
36: private ?Type $subtractedType;
37:
38: private ?ObjectType $staticObjectType = null;
39:
40: private string $baseClass;
41:
42: /** @var array<string, ExtendedMethodReflection> */
43: private array $methodCache = [];
44:
45: /**
46: * @api
47: */
48: public function __construct(
49: private ClassReflection $classReflection,
50: ?Type $subtractedType = null,
51: )
52: {
53: if ($subtractedType instanceof NeverType) {
54: $subtractedType = null;
55: }
56:
57: $this->subtractedType = $subtractedType;
58: $this->baseClass = $classReflection->getName();
59: }
60:
61: public function getClassName(): string
62: {
63: return $this->baseClass;
64: }
65:
66: public function getClassReflection(): ClassReflection
67: {
68: return $this->classReflection;
69: }
70:
71: public function getAncestorWithClassName(string $className): ?TypeWithClassName
72: {
73: $ancestor = $this->getStaticObjectType()->getAncestorWithClassName($className);
74: if ($ancestor === null) {
75: return null;
76: }
77:
78: $classReflection = $ancestor->getClassReflection();
79: if ($classReflection !== null) {
80: return $this->changeBaseClass($classReflection);
81: }
82:
83: return null;
84: }
85:
86: public function getStaticObjectType(): ObjectType
87: {
88: if ($this->staticObjectType === null) {
89: if ($this->classReflection->isGeneric()) {
90: $typeMap = $this->classReflection->getActiveTemplateTypeMap()->map(static fn (string $name, Type $type): Type => TemplateTypeHelper::toArgument($type));
91: $varianceMap = $this->classReflection->getCallSiteVarianceMap();
92: return $this->staticObjectType = new GenericObjectType(
93: $this->classReflection->getName(),
94: $this->classReflection->typeMapToList($typeMap),
95: $this->subtractedType,
96: variances: $this->classReflection->varianceMapToList($varianceMap),
97: );
98: }
99:
100: return $this->staticObjectType = new ObjectType($this->classReflection->getName(), $this->subtractedType, $this->classReflection);
101: }
102:
103: return $this->staticObjectType;
104: }
105:
106: public function getReferencedClasses(): array
107: {
108: return $this->getStaticObjectType()->getReferencedClasses();
109: }
110:
111: public function getObjectClassNames(): array
112: {
113: return $this->getStaticObjectType()->getObjectClassNames();
114: }
115:
116: public function getObjectClassReflections(): array
117: {
118: return $this->getStaticObjectType()->getObjectClassReflections();
119: }
120:
121: public function getArrays(): array
122: {
123: return $this->getStaticObjectType()->getArrays();
124: }
125:
126: public function getConstantArrays(): array
127: {
128: return $this->getStaticObjectType()->getConstantArrays();
129: }
130:
131: public function getConstantStrings(): array
132: {
133: return $this->getStaticObjectType()->getConstantStrings();
134: }
135:
136: public function accepts(Type $type, bool $strictTypes): AcceptsResult
137: {
138: if ($type instanceof CompoundType) {
139: return $type->isAcceptedBy($this, $strictTypes);
140: }
141:
142: if (!$type instanceof static) {
143: return AcceptsResult::createNo();
144: }
145:
146: return $this->getStaticObjectType()->accepts($type->getStaticObjectType(), $strictTypes);
147: }
148:
149: public function isSuperTypeOf(Type $type): IsSuperTypeOfResult
150: {
151: if ($type instanceof self) {
152: return $this->getStaticObjectType()->isSuperTypeOf($type);
153: }
154:
155: if ($type instanceof ObjectWithoutClassType) {
156: return IsSuperTypeOfResult::createMaybe();
157: }
158:
159: if ($type instanceof ObjectType) {
160: $result = $this->getStaticObjectType()->isSuperTypeOf($type);
161: if ($result->yes()) {
162: $classReflection = $type->getClassReflection();
163: if ($classReflection !== null && $classReflection->isFinal()) {
164: return $result;
165: }
166: }
167:
168: return $result->and(IsSuperTypeOfResult::createMaybe());
169: }
170:
171: if ($type instanceof CompoundType) {
172: return $type->isSubTypeOf($this);
173: }
174:
175: return IsSuperTypeOfResult::createNo();
176: }
177:
178: public function equals(Type $type): bool
179: {
180: if (get_class($type) !== static::class) {
181: return false;
182: }
183:
184: return $this->getStaticObjectType()->equals($type->getStaticObjectType());
185: }
186:
187: public function describe(VerbosityLevel $level): string
188: {
189: return sprintf('static(%s)', $this->getStaticObjectType()->describe($level));
190: }
191:
192: public function getTemplateType(string $ancestorClassName, string $templateTypeName): Type
193: {
194: return $this->getStaticObjectType()->getTemplateType($ancestorClassName, $templateTypeName);
195: }
196:
197: public function isObject(): TrinaryLogic
198: {
199: return $this->getStaticObjectType()->isObject();
200: }
201:
202: public function getClassStringType(): Type
203: {
204: return new GenericClassStringType($this);
205: }
206:
207: public function isEnum(): TrinaryLogic
208: {
209: return $this->getStaticObjectType()->isEnum();
210: }
211:
212: public function canAccessProperties(): TrinaryLogic
213: {
214: return $this->getStaticObjectType()->canAccessProperties();
215: }
216:
217: public function hasProperty(string $propertyName): TrinaryLogic
218: {
219: return $this->getStaticObjectType()->hasProperty($propertyName);
220: }
221:
222: public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
223: {
224: return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty();
225: }
226:
227: public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
228: {
229: $staticObject = $this->getStaticObjectType();
230: $nakedProperty = $staticObject->getUnresolvedPropertyPrototype($propertyName, $scope)->getNakedProperty();
231:
232: $ancestor = $this->getAncestorWithClassName($nakedProperty->getDeclaringClass()->getName());
233: $classReflection = null;
234: if ($ancestor !== null) {
235: $classReflection = $ancestor->getClassReflection();
236: }
237: if ($classReflection === null) {
238: $classReflection = $nakedProperty->getDeclaringClass();
239: }
240:
241: return new CallbackUnresolvedPropertyPrototypeReflection(
242: $nakedProperty,
243: $classReflection,
244: false,
245: fn (Type $type): Type => $this->transformStaticType($type, $scope),
246: );
247: }
248:
249: public function hasInstanceProperty(string $propertyName): TrinaryLogic
250: {
251: return $this->getStaticObjectType()->hasInstanceProperty($propertyName);
252: }
253:
254: public function getInstanceProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
255: {
256: return $this->getUnresolvedInstancePropertyPrototype($propertyName, $scope)->getTransformedProperty();
257: }
258:
259: public function getUnresolvedInstancePropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
260: {
261: $staticObject = $this->getStaticObjectType();
262: $nakedProperty = $staticObject->getUnresolvedInstancePropertyPrototype($propertyName, $scope)->getNakedProperty();
263:
264: $ancestor = $this->getAncestorWithClassName($nakedProperty->getDeclaringClass()->getName());
265: $classReflection = null;
266: if ($ancestor !== null) {
267: $classReflection = $ancestor->getClassReflection();
268: }
269: if ($classReflection === null) {
270: $classReflection = $nakedProperty->getDeclaringClass();
271: }
272:
273: return new CallbackUnresolvedPropertyPrototypeReflection(
274: $nakedProperty,
275: $classReflection,
276: false,
277: fn (Type $type): Type => $this->transformStaticType($type, $scope),
278: );
279: }
280:
281: public function hasStaticProperty(string $propertyName): TrinaryLogic
282: {
283: return $this->getStaticObjectType()->hasStaticProperty($propertyName);
284: }
285:
286: public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
287: {
288: return $this->getUnresolvedStaticPropertyPrototype($propertyName, $scope)->getTransformedProperty();
289: }
290:
291: public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
292: {
293: $staticObject = $this->getStaticObjectType();
294: $nakedProperty = $staticObject->getUnresolvedStaticPropertyPrototype($propertyName, $scope)->getNakedProperty();
295:
296: $ancestor = $this->getAncestorWithClassName($nakedProperty->getDeclaringClass()->getName());
297: $classReflection = null;
298: if ($ancestor !== null) {
299: $classReflection = $ancestor->getClassReflection();
300: }
301: if ($classReflection === null) {
302: $classReflection = $nakedProperty->getDeclaringClass();
303: }
304:
305: return new CallbackUnresolvedPropertyPrototypeReflection(
306: $nakedProperty,
307: $classReflection,
308: false,
309: fn (Type $type): Type => $this->transformStaticType($type, $scope),
310: );
311: }
312:
313: public function canCallMethods(): TrinaryLogic
314: {
315: return $this->getStaticObjectType()->canCallMethods();
316: }
317:
318: public function hasMethod(string $methodName): TrinaryLogic
319: {
320: return $this->getStaticObjectType()->hasMethod($methodName);
321: }
322:
323: public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): ExtendedMethodReflection
324: {
325: $key = $methodName;
326: if ($scope->isInClass()) {
327: $key = sprintf('%s-%s', $key, $scope->getClassReflection()->getCacheKey());
328: }
329: return $this->methodCache[$key] ??= $this->getUnresolvedMethodPrototype($methodName, $scope)->getTransformedMethod();
330: }
331:
332: public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection
333: {
334: $staticObject = $this->getStaticObjectType();
335: $nakedMethod = $staticObject->getUnresolvedMethodPrototype($methodName, $scope)->getNakedMethod();
336:
337: $ancestor = $this->getAncestorWithClassName($nakedMethod->getDeclaringClass()->getName());
338: $classReflection = null;
339: if ($ancestor !== null) {
340: $classReflection = $ancestor->getClassReflection();
341: }
342: if ($classReflection === null) {
343: $classReflection = $nakedMethod->getDeclaringClass();
344: }
345:
346: return new CallbackUnresolvedMethodPrototypeReflection(
347: $nakedMethod,
348: $classReflection,
349: false,
350: fn (Type $type): Type => $this->transformStaticType($type, $scope),
351: );
352: }
353:
354: private function transformStaticType(Type $type, ClassMemberAccessAnswerer $scope): Type
355: {
356: return TypeTraverser::map($type, function (Type $type, callable $traverse) use ($scope): Type {
357: if ($type instanceof StaticType) {
358: $classReflection = $this->classReflection;
359: $isFinal = false;
360: if ($scope->isInClass()) {
361: $classReflection = $scope->getClassReflection();
362: $isFinal = $classReflection->isFinal();
363: }
364: $type = $type->changeBaseClass($classReflection);
365:
366: // When calling a method on a `static` type (not `$this`),
367: // `$this` return type should be downgraded to `static`
368: // because we can't guarantee the exact instance.
369: if ($type instanceof ThisType && !$this instanceof ThisType) {
370: $type = new self($type->getClassReflection(), $type->getSubtractedType());
371: }
372:
373: if ($this->getSubtractedType() !== null) {
374: $type = $type->subtract($this->getSubtractedType());
375: if (!$type instanceof StaticType) {
376: return $traverse($type);
377: }
378: }
379:
380: if (!$isFinal || $type instanceof ThisType) {
381: return $traverse($type);
382: }
383:
384: return $traverse($type->getStaticObjectType());
385: }
386:
387: return $traverse($type);
388: });
389: }
390:
391: public function canAccessConstants(): TrinaryLogic
392: {
393: return $this->getStaticObjectType()->canAccessConstants();
394: }
395:
396: public function hasConstant(string $constantName): TrinaryLogic
397: {
398: return $this->getStaticObjectType()->hasConstant($constantName);
399: }
400:
401: public function getConstant(string $constantName): ClassConstantReflection
402: {
403: return $this->getStaticObjectType()->getConstant($constantName);
404: }
405:
406: public function changeBaseClass(ClassReflection $classReflection): self
407: {
408: return new self($classReflection, $this->subtractedType);
409: }
410:
411: public function isIterable(): TrinaryLogic
412: {
413: return $this->getStaticObjectType()->isIterable();
414: }
415:
416: public function isIterableAtLeastOnce(): TrinaryLogic
417: {
418: return $this->getStaticObjectType()->isIterableAtLeastOnce();
419: }
420:
421: public function getArraySize(): Type
422: {
423: return $this->getStaticObjectType()->getArraySize();
424: }
425:
426: public function getIterableKeyType(): Type
427: {
428: return $this->getStaticObjectType()->getIterableKeyType();
429: }
430:
431: public function getFirstIterableKeyType(): Type
432: {
433: return $this->getStaticObjectType()->getIterableKeyType();
434: }
435:
436: public function getLastIterableKeyType(): Type
437: {
438: return $this->getStaticObjectType()->getIterableKeyType();
439: }
440:
441: public function getIterableValueType(): Type
442: {
443: return $this->getStaticObjectType()->getIterableValueType();
444: }
445:
446: public function getFirstIterableValueType(): Type
447: {
448: return $this->getStaticObjectType()->getIterableValueType();
449: }
450:
451: public function getLastIterableValueType(): Type
452: {
453: return $this->getStaticObjectType()->getIterableValueType();
454: }
455:
456: public function isOffsetAccessible(): TrinaryLogic
457: {
458: return $this->getStaticObjectType()->isOffsetAccessible();
459: }
460:
461: public function isOffsetAccessLegal(): TrinaryLogic
462: {
463: return $this->getStaticObjectType()->isOffsetAccessLegal();
464: }
465:
466: public function hasOffsetValueType(Type $offsetType): TrinaryLogic
467: {
468: return $this->getStaticObjectType()->hasOffsetValueType($offsetType);
469: }
470:
471: public function getOffsetValueType(Type $offsetType): Type
472: {
473: return $this->getStaticObjectType()->getOffsetValueType($offsetType);
474: }
475:
476: public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type
477: {
478: return $this->getStaticObjectType()->setOffsetValueType($offsetType, $valueType, $unionValues);
479: }
480:
481: public function setExistingOffsetValueType(Type $offsetType, Type $valueType): Type
482: {
483: return $this->getStaticObjectType()->setExistingOffsetValueType($offsetType, $valueType);
484: }
485:
486: public function unsetOffset(Type $offsetType): Type
487: {
488: return $this->getStaticObjectType()->unsetOffset($offsetType);
489: }
490:
491: public function getKeysArrayFiltered(Type $filterValueType, TrinaryLogic $strict): Type
492: {
493: return $this->getStaticObjectType()->getKeysArrayFiltered($filterValueType, $strict);
494: }
495:
496: public function getKeysArray(): Type
497: {
498: return $this->getStaticObjectType()->getKeysArray();
499: }
500:
501: public function getValuesArray(): Type
502: {
503: return $this->getStaticObjectType()->getValuesArray();
504: }
505:
506: public function chunkArray(Type $lengthType, TrinaryLogic $preserveKeys): Type
507: {
508: return $this->getStaticObjectType()->chunkArray($lengthType, $preserveKeys);
509: }
510:
511: public function fillKeysArray(Type $valueType): Type
512: {
513: return $this->getStaticObjectType()->fillKeysArray($valueType);
514: }
515:
516: public function flipArray(): Type
517: {
518: return $this->getStaticObjectType()->flipArray();
519: }
520:
521: public function intersectKeyArray(Type $otherArraysType): Type
522: {
523: return $this->getStaticObjectType()->intersectKeyArray($otherArraysType);
524: }
525:
526: public function popArray(): Type
527: {
528: return $this->getStaticObjectType()->popArray();
529: }
530:
531: public function reverseArray(TrinaryLogic $preserveKeys): Type
532: {
533: return $this->getStaticObjectType()->reverseArray($preserveKeys);
534: }
535:
536: public function searchArray(Type $needleType, ?TrinaryLogic $strict = null): Type
537: {
538: return $this->getStaticObjectType()->searchArray($needleType, $strict);
539: }
540:
541: public function shiftArray(): Type
542: {
543: return $this->getStaticObjectType()->shiftArray();
544: }
545:
546: public function shuffleArray(): Type
547: {
548: return $this->getStaticObjectType()->shuffleArray();
549: }
550:
551: public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $preserveKeys): Type
552: {
553: return $this->getStaticObjectType()->sliceArray($offsetType, $lengthType, $preserveKeys);
554: }
555:
556: public function spliceArray(Type $offsetType, Type $lengthType, Type $replacementType): Type
557: {
558: return $this->getStaticObjectType()->spliceArray($offsetType, $lengthType, $replacementType);
559: }
560:
561: public function isCallable(): TrinaryLogic
562: {
563: return $this->getStaticObjectType()->isCallable();
564: }
565:
566: public function getEnumCases(): array
567: {
568: return $this->getStaticObjectType()->getEnumCases();
569: }
570:
571: public function getEnumCaseObject(): ?EnumCaseObjectType
572: {
573: return $this->getStaticObjectType()->getEnumCaseObject();
574: }
575:
576: public function isArray(): TrinaryLogic
577: {
578: return $this->getStaticObjectType()->isArray();
579: }
580:
581: public function isConstantArray(): TrinaryLogic
582: {
583: return $this->getStaticObjectType()->isConstantArray();
584: }
585:
586: public function isOversizedArray(): TrinaryLogic
587: {
588: return $this->getStaticObjectType()->isOversizedArray();
589: }
590:
591: public function isList(): TrinaryLogic
592: {
593: return $this->getStaticObjectType()->isList();
594: }
595:
596: public function isNull(): TrinaryLogic
597: {
598: return $this->getStaticObjectType()->isNull();
599: }
600:
601: public function isConstantValue(): TrinaryLogic
602: {
603: return $this->getStaticObjectType()->isConstantValue();
604: }
605:
606: public function isConstantScalarValue(): TrinaryLogic
607: {
608: return $this->getStaticObjectType()->isConstantScalarValue();
609: }
610:
611: public function getConstantScalarTypes(): array
612: {
613: return $this->getStaticObjectType()->getConstantScalarTypes();
614: }
615:
616: public function getConstantScalarValues(): array
617: {
618: return $this->getStaticObjectType()->getConstantScalarValues();
619: }
620:
621: public function isTrue(): TrinaryLogic
622: {
623: return $this->getStaticObjectType()->isTrue();
624: }
625:
626: public function isFalse(): TrinaryLogic
627: {
628: return $this->getStaticObjectType()->isFalse();
629: }
630:
631: public function isBoolean(): TrinaryLogic
632: {
633: return $this->getStaticObjectType()->isBoolean();
634: }
635:
636: public function isFloat(): TrinaryLogic
637: {
638: return $this->getStaticObjectType()->isFloat();
639: }
640:
641: public function isInteger(): TrinaryLogic
642: {
643: return $this->getStaticObjectType()->isInteger();
644: }
645:
646: public function isString(): TrinaryLogic
647: {
648: return $this->getStaticObjectType()->isString();
649: }
650:
651: public function isNumericString(): TrinaryLogic
652: {
653: return $this->getStaticObjectType()->isNumericString();
654: }
655:
656: public function isNonEmptyString(): TrinaryLogic
657: {
658: return $this->getStaticObjectType()->isNonEmptyString();
659: }
660:
661: public function isNonFalsyString(): TrinaryLogic
662: {
663: return $this->getStaticObjectType()->isNonFalsyString();
664: }
665:
666: public function isLiteralString(): TrinaryLogic
667: {
668: return $this->getStaticObjectType()->isLiteralString();
669: }
670:
671: public function isLowercaseString(): TrinaryLogic
672: {
673: return $this->getStaticObjectType()->isLowercaseString();
674: }
675:
676: public function isUppercaseString(): TrinaryLogic
677: {
678: return $this->getStaticObjectType()->isUppercaseString();
679: }
680:
681: public function isClassString(): TrinaryLogic
682: {
683: return $this->getStaticObjectType()->isClassString();
684: }
685:
686: public function getClassStringObjectType(): Type
687: {
688: return $this->getStaticObjectType()->getClassStringObjectType();
689: }
690:
691: public function getObjectTypeOrClassStringObjectType(): Type
692: {
693: return $this;
694: }
695:
696: public function isVoid(): TrinaryLogic
697: {
698: return $this->getStaticObjectType()->isVoid();
699: }
700:
701: public function isScalar(): TrinaryLogic
702: {
703: return $this->getStaticObjectType()->isScalar();
704: }
705:
706: public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType
707: {
708: return new BooleanType();
709: }
710:
711: public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array
712: {
713: return $this->getStaticObjectType()->getCallableParametersAcceptors($scope);
714: }
715:
716: public function isCloneable(): TrinaryLogic
717: {
718: return TrinaryLogic::createYes();
719: }
720:
721: public function toNumber(): Type
722: {
723: return new ErrorType();
724: }
725:
726: public function toAbsoluteNumber(): Type
727: {
728: return new ErrorType();
729: }
730:
731: public function toString(): Type
732: {
733: return $this->getStaticObjectType()->toString();
734: }
735:
736: public function toInteger(): Type
737: {
738: return new ErrorType();
739: }
740:
741: public function toFloat(): Type
742: {
743: return new ErrorType();
744: }
745:
746: public function toArray(): Type
747: {
748: return $this->getStaticObjectType()->toArray();
749: }
750:
751: public function toArrayKey(): Type
752: {
753: return $this->getStaticObjectType()->toArrayKey();
754: }
755:
756: public function toCoercedArgumentType(bool $strictTypes): Type
757: {
758: return $this->getStaticObjectType()->toCoercedArgumentType($strictTypes);
759: }
760:
761: public function toBoolean(): BooleanType
762: {
763: return $this->getStaticObjectType()->toBoolean();
764: }
765:
766: public function traverse(callable $cb): Type
767: {
768: $subtractedType = $this->subtractedType !== null ? $cb($this->subtractedType) : null;
769:
770: if ($subtractedType !== $this->subtractedType) {
771: return new self(
772: $this->classReflection,
773: $subtractedType,
774: );
775: }
776:
777: return $this;
778: }
779:
780: public function traverseSimultaneously(Type $right, callable $cb): Type
781: {
782: if ($this->subtractedType === null) {
783: return $this;
784: }
785:
786: return new self($this->classReflection);
787: }
788:
789: public function subtract(Type $type): Type
790: {
791: if ($this->subtractedType !== null) {
792: $type = TypeCombinator::union($this->subtractedType, $type);
793: }
794:
795: return $this->changeSubtractedType($type);
796: }
797:
798: public function getTypeWithoutSubtractedType(): Type
799: {
800: return $this->changeSubtractedType(null);
801: }
802:
803: public function changeSubtractedType(?Type $subtractedType): Type
804: {
805: if ($subtractedType !== null) {
806: $classReflection = $this->getClassReflection();
807: if ($classReflection->getAllowedSubTypes() !== null) {
808: $objectType = $this->getStaticObjectType()->changeSubtractedType($subtractedType);
809: if ($objectType instanceof NeverType) {
810: return $objectType;
811: }
812:
813: if ($objectType instanceof ObjectType && $objectType->getSubtractedType() !== null) {
814: return new self($classReflection, $objectType->getSubtractedType());
815: }
816:
817: return TypeCombinator::intersect($this, $objectType);
818: }
819: }
820:
821: return new self($this->classReflection, $subtractedType);
822: }
823:
824: public function getSubtractedType(): ?Type
825: {
826: return $this->subtractedType;
827: }
828:
829: public function tryRemove(Type $typeToRemove): ?Type
830: {
831: if ($this->getStaticObjectType()->isSuperTypeOf($typeToRemove)->yes()) {
832: return $this->subtract($typeToRemove);
833: }
834:
835: return null;
836: }
837:
838: public function exponentiate(Type $exponent): Type
839: {
840: return $this->getStaticObjectType()->exponentiate($exponent);
841: }
842:
843: public function getFiniteTypes(): array
844: {
845: return $this->getStaticObjectType()->getFiniteTypes();
846: }
847:
848: public function toPhpDocNode(): TypeNode
849: {
850: return new IdentifierTypeNode('static');
851: }
852:
853: public function hasTemplateOrLateResolvableType(): bool
854: {
855: return $this->getStaticObjectType()->hasTemplateOrLateResolvableType();
856: }
857:
858: }
859: