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\ClassMemberAccessAnswerer;
9: use PHPStan\Reflection\ClassReflection;
10: use PHPStan\Reflection\ConstantReflection;
11: use PHPStan\Reflection\ExtendedMethodReflection;
12: use PHPStan\Reflection\PropertyReflection;
13: use PHPStan\Reflection\ReflectionProviderStaticAccessor;
14: use PHPStan\Reflection\Type\CallbackUnresolvedMethodPrototypeReflection;
15: use PHPStan\Reflection\Type\CallbackUnresolvedPropertyPrototypeReflection;
16: use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection;
17: use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection;
18: use PHPStan\TrinaryLogic;
19: use PHPStan\Type\Generic\GenericObjectType;
20: use PHPStan\Type\Generic\TemplateTypeHelper;
21: use PHPStan\Type\Traits\NonGeneralizableTypeTrait;
22: use PHPStan\Type\Traits\NonGenericTypeTrait;
23: use PHPStan\Type\Traits\UndecidedComparisonTypeTrait;
24: use function get_class;
25: use function sprintf;
26:
27: /** @api */
28: class StaticType implements TypeWithClassName, SubtractableType
29: {
30:
31: use NonGenericTypeTrait;
32: use UndecidedComparisonTypeTrait;
33: use NonGeneralizableTypeTrait;
34:
35: private ?Type $subtractedType;
36:
37: private ?ObjectType $staticObjectType = null;
38:
39: private string $baseClass;
40:
41: /**
42: * @api
43: */
44: public function __construct(
45: private ClassReflection $classReflection,
46: ?Type $subtractedType = null,
47: )
48: {
49: if ($subtractedType instanceof NeverType) {
50: $subtractedType = null;
51: }
52:
53: $this->subtractedType = $subtractedType;
54: $this->baseClass = $classReflection->getName();
55: }
56:
57: public function getClassName(): string
58: {
59: return $this->baseClass;
60: }
61:
62: public function getClassReflection(): ClassReflection
63: {
64: return $this->classReflection;
65: }
66:
67: public function getAncestorWithClassName(string $className): ?TypeWithClassName
68: {
69: $ancestor = $this->getStaticObjectType()->getAncestorWithClassName($className);
70: if ($ancestor === null) {
71: return null;
72: }
73:
74: $classReflection = $ancestor->getClassReflection();
75: if ($classReflection !== null) {
76: return $this->changeBaseClass($classReflection);
77: }
78:
79: return null;
80: }
81:
82: public function getStaticObjectType(): ObjectType
83: {
84: if ($this->staticObjectType === null) {
85: if ($this->classReflection->isGeneric()) {
86: $typeMap = $this->classReflection->getActiveTemplateTypeMap()->map(static fn (string $name, Type $type): Type => TemplateTypeHelper::toArgument($type));
87: $varianceMap = $this->classReflection->getCallSiteVarianceMap();
88: return $this->staticObjectType = new GenericObjectType(
89: $this->classReflection->getName(),
90: $this->classReflection->typeMapToList($typeMap),
91: $this->subtractedType,
92: null,
93: $this->classReflection->varianceMapToList($varianceMap),
94: );
95: }
96:
97: return $this->staticObjectType = new ObjectType($this->classReflection->getName(), $this->subtractedType, $this->classReflection);
98: }
99:
100: return $this->staticObjectType;
101: }
102:
103: /**
104: * @return string[]
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): TrinaryLogic
137: {
138: return $this->acceptsWithReason($type, $strictTypes)->result;
139: }
140:
141: public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult
142: {
143: if ($type instanceof CompoundType) {
144: return $type->isAcceptedWithReasonBy($this, $strictTypes);
145: }
146:
147: if (!$type instanceof static) {
148: return AcceptsResult::createNo();
149: }
150:
151: return $this->getStaticObjectType()->acceptsWithReason($type->getStaticObjectType(), $strictTypes);
152: }
153:
154: public function isSuperTypeOf(Type $type): TrinaryLogic
155: {
156: if ($type instanceof self) {
157: return $this->getStaticObjectType()->isSuperTypeOf($type);
158: }
159:
160: if ($type instanceof ObjectWithoutClassType) {
161: return TrinaryLogic::createMaybe();
162: }
163:
164: if ($type instanceof ObjectType) {
165: $result = $this->getStaticObjectType()->isSuperTypeOf($type);
166: if ($result->yes()) {
167: $classReflection = $type->getClassReflection();
168: if ($classReflection !== null && $classReflection->isFinal()) {
169: return $result;
170: }
171: }
172:
173: return $result->and(TrinaryLogic::createMaybe());
174: }
175:
176: if ($type instanceof CompoundType) {
177: return $type->isSubTypeOf($this);
178: }
179:
180: return TrinaryLogic::createNo();
181: }
182:
183: public function equals(Type $type): bool
184: {
185: if (get_class($type) !== static::class) {
186: return false;
187: }
188:
189: return $this->getStaticObjectType()->equals($type->getStaticObjectType());
190: }
191:
192: public function describe(VerbosityLevel $level): string
193: {
194: return sprintf('static(%s)', $this->getStaticObjectType()->describe($level));
195: }
196:
197: public function getTemplateType(string $ancestorClassName, string $templateTypeName): Type
198: {
199: return $this->getStaticObjectType()->getTemplateType($ancestorClassName, $templateTypeName);
200: }
201:
202: public function isObject(): TrinaryLogic
203: {
204: return $this->getStaticObjectType()->isObject();
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): PropertyReflection
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 canCallMethods(): TrinaryLogic
250: {
251: return $this->getStaticObjectType()->canCallMethods();
252: }
253:
254: public function hasMethod(string $methodName): TrinaryLogic
255: {
256: return $this->getStaticObjectType()->hasMethod($methodName);
257: }
258:
259: public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): ExtendedMethodReflection
260: {
261: return $this->getUnresolvedMethodPrototype($methodName, $scope)->getTransformedMethod();
262: }
263:
264: public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection
265: {
266: $staticObject = $this->getStaticObjectType();
267: $nakedMethod = $staticObject->getUnresolvedMethodPrototype($methodName, $scope)->getNakedMethod();
268:
269: $ancestor = $this->getAncestorWithClassName($nakedMethod->getDeclaringClass()->getName());
270: $classReflection = null;
271: if ($ancestor !== null) {
272: $classReflection = $ancestor->getClassReflection();
273: }
274: if ($classReflection === null) {
275: $classReflection = $nakedMethod->getDeclaringClass();
276: }
277:
278: return new CallbackUnresolvedMethodPrototypeReflection(
279: $nakedMethod,
280: $classReflection,
281: false,
282: fn (Type $type): Type => $this->transformStaticType($type, $scope),
283: );
284: }
285:
286: private function transformStaticType(Type $type, ClassMemberAccessAnswerer $scope): Type
287: {
288: return TypeTraverser::map($type, function (Type $type, callable $traverse) use ($scope): Type {
289: if ($type instanceof StaticType) {
290: $classReflection = $this->classReflection;
291: $isFinal = false;
292: if ($scope->isInClass()) {
293: $classReflection = $scope->getClassReflection();
294: $isFinal = $classReflection->isFinal();
295: }
296: $type = $type->changeBaseClass($classReflection);
297: if (!$isFinal) {
298: return $type;
299: }
300:
301: return $type->getStaticObjectType();
302: }
303:
304: return $traverse($type);
305: });
306: }
307:
308: public function canAccessConstants(): TrinaryLogic
309: {
310: return $this->getStaticObjectType()->canAccessConstants();
311: }
312:
313: public function hasConstant(string $constantName): TrinaryLogic
314: {
315: return $this->getStaticObjectType()->hasConstant($constantName);
316: }
317:
318: public function getConstant(string $constantName): ConstantReflection
319: {
320: return $this->getStaticObjectType()->getConstant($constantName);
321: }
322:
323: public function changeBaseClass(ClassReflection $classReflection): self
324: {
325: return new self($classReflection, $this->subtractedType);
326: }
327:
328: public function isIterable(): TrinaryLogic
329: {
330: return $this->getStaticObjectType()->isIterable();
331: }
332:
333: public function isIterableAtLeastOnce(): TrinaryLogic
334: {
335: return $this->getStaticObjectType()->isIterableAtLeastOnce();
336: }
337:
338: public function getArraySize(): Type
339: {
340: return $this->getStaticObjectType()->getArraySize();
341: }
342:
343: public function getIterableKeyType(): Type
344: {
345: return $this->getStaticObjectType()->getIterableKeyType();
346: }
347:
348: public function getFirstIterableKeyType(): Type
349: {
350: return $this->getStaticObjectType()->getFirstIterableKeyType();
351: }
352:
353: public function getLastIterableKeyType(): Type
354: {
355: return $this->getStaticObjectType()->getLastIterableKeyType();
356: }
357:
358: public function getIterableValueType(): Type
359: {
360: return $this->getStaticObjectType()->getIterableValueType();
361: }
362:
363: public function getFirstIterableValueType(): Type
364: {
365: return $this->getStaticObjectType()->getFirstIterableValueType();
366: }
367:
368: public function getLastIterableValueType(): Type
369: {
370: return $this->getStaticObjectType()->getLastIterableValueType();
371: }
372:
373: public function isOffsetAccessible(): TrinaryLogic
374: {
375: return $this->getStaticObjectType()->isOffsetAccessible();
376: }
377:
378: public function hasOffsetValueType(Type $offsetType): TrinaryLogic
379: {
380: return $this->getStaticObjectType()->hasOffsetValueType($offsetType);
381: }
382:
383: public function getOffsetValueType(Type $offsetType): Type
384: {
385: return $this->getStaticObjectType()->getOffsetValueType($offsetType);
386: }
387:
388: public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type
389: {
390: return $this->getStaticObjectType()->setOffsetValueType($offsetType, $valueType, $unionValues);
391: }
392:
393: public function setExistingOffsetValueType(Type $offsetType, Type $valueType): Type
394: {
395: return $this->getStaticObjectType()->setExistingOffsetValueType($offsetType, $valueType);
396: }
397:
398: public function unsetOffset(Type $offsetType): Type
399: {
400: return $this->getStaticObjectType()->unsetOffset($offsetType);
401: }
402:
403: public function getKeysArray(): Type
404: {
405: return $this->getStaticObjectType()->getKeysArray();
406: }
407:
408: public function getValuesArray(): Type
409: {
410: return $this->getStaticObjectType()->getValuesArray();
411: }
412:
413: public function fillKeysArray(Type $valueType): Type
414: {
415: return $this->getStaticObjectType()->fillKeysArray($valueType);
416: }
417:
418: public function flipArray(): Type
419: {
420: return $this->getStaticObjectType()->flipArray();
421: }
422:
423: public function intersectKeyArray(Type $otherArraysType): Type
424: {
425: return $this->getStaticObjectType()->intersectKeyArray($otherArraysType);
426: }
427:
428: public function popArray(): Type
429: {
430: return $this->getStaticObjectType()->popArray();
431: }
432:
433: public function searchArray(Type $needleType): Type
434: {
435: return $this->getStaticObjectType()->searchArray($needleType);
436: }
437:
438: public function shiftArray(): Type
439: {
440: return $this->getStaticObjectType()->shiftArray();
441: }
442:
443: public function shuffleArray(): Type
444: {
445: return $this->getStaticObjectType()->shuffleArray();
446: }
447:
448: public function isCallable(): TrinaryLogic
449: {
450: return $this->getStaticObjectType()->isCallable();
451: }
452:
453: public function getEnumCases(): array
454: {
455: return $this->getStaticObjectType()->getEnumCases();
456: }
457:
458: public function isArray(): TrinaryLogic
459: {
460: return $this->getStaticObjectType()->isArray();
461: }
462:
463: public function isConstantArray(): TrinaryLogic
464: {
465: return $this->getStaticObjectType()->isConstantArray();
466: }
467:
468: public function isOversizedArray(): TrinaryLogic
469: {
470: return $this->getStaticObjectType()->isOversizedArray();
471: }
472:
473: public function isList(): TrinaryLogic
474: {
475: return $this->getStaticObjectType()->isList();
476: }
477:
478: public function isNull(): TrinaryLogic
479: {
480: return $this->getStaticObjectType()->isNull();
481: }
482:
483: public function isConstantValue(): TrinaryLogic
484: {
485: return $this->getStaticObjectType()->isConstantValue();
486: }
487:
488: public function isConstantScalarValue(): TrinaryLogic
489: {
490: return $this->getStaticObjectType()->isConstantScalarValue();
491: }
492:
493: public function getConstantScalarTypes(): array
494: {
495: return $this->getStaticObjectType()->getConstantScalarTypes();
496: }
497:
498: public function getConstantScalarValues(): array
499: {
500: return $this->getStaticObjectType()->getConstantScalarValues();
501: }
502:
503: public function isTrue(): TrinaryLogic
504: {
505: return $this->getStaticObjectType()->isTrue();
506: }
507:
508: public function isFalse(): TrinaryLogic
509: {
510: return $this->getStaticObjectType()->isFalse();
511: }
512:
513: public function isBoolean(): TrinaryLogic
514: {
515: return $this->getStaticObjectType()->isBoolean();
516: }
517:
518: public function isFloat(): TrinaryLogic
519: {
520: return $this->getStaticObjectType()->isFloat();
521: }
522:
523: public function isInteger(): TrinaryLogic
524: {
525: return $this->getStaticObjectType()->isInteger();
526: }
527:
528: public function isString(): TrinaryLogic
529: {
530: return $this->getStaticObjectType()->isString();
531: }
532:
533: public function isNumericString(): TrinaryLogic
534: {
535: return $this->getStaticObjectType()->isNumericString();
536: }
537:
538: public function isNonEmptyString(): TrinaryLogic
539: {
540: return $this->getStaticObjectType()->isNonEmptyString();
541: }
542:
543: public function isNonFalsyString(): TrinaryLogic
544: {
545: return $this->getStaticObjectType()->isNonFalsyString();
546: }
547:
548: public function isLiteralString(): TrinaryLogic
549: {
550: return $this->getStaticObjectType()->isLiteralString();
551: }
552:
553: public function isClassStringType(): TrinaryLogic
554: {
555: return $this->getStaticObjectType()->isClassStringType();
556: }
557:
558: public function getClassStringObjectType(): Type
559: {
560: return $this->getStaticObjectType()->getClassStringObjectType();
561: }
562:
563: public function getObjectTypeOrClassStringObjectType(): Type
564: {
565: return $this;
566: }
567:
568: public function isVoid(): TrinaryLogic
569: {
570: return $this->getStaticObjectType()->isVoid();
571: }
572:
573: public function isScalar(): TrinaryLogic
574: {
575: return $this->getStaticObjectType()->isScalar();
576: }
577:
578: public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType
579: {
580: return new BooleanType();
581: }
582:
583: public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array
584: {
585: return $this->getStaticObjectType()->getCallableParametersAcceptors($scope);
586: }
587:
588: public function isCloneable(): TrinaryLogic
589: {
590: return TrinaryLogic::createYes();
591: }
592:
593: public function toNumber(): Type
594: {
595: return new ErrorType();
596: }
597:
598: public function toString(): Type
599: {
600: return $this->getStaticObjectType()->toString();
601: }
602:
603: public function toInteger(): Type
604: {
605: return new ErrorType();
606: }
607:
608: public function toFloat(): Type
609: {
610: return new ErrorType();
611: }
612:
613: public function toArray(): Type
614: {
615: return $this->getStaticObjectType()->toArray();
616: }
617:
618: public function toArrayKey(): Type
619: {
620: return $this->getStaticObjectType()->toArrayKey();
621: }
622:
623: public function toBoolean(): BooleanType
624: {
625: return $this->getStaticObjectType()->toBoolean();
626: }
627:
628: public function traverse(callable $cb): Type
629: {
630: $subtractedType = $this->subtractedType !== null ? $cb($this->subtractedType) : null;
631:
632: if ($subtractedType !== $this->subtractedType) {
633: return new self(
634: $this->classReflection,
635: $subtractedType,
636: );
637: }
638:
639: return $this;
640: }
641:
642: public function traverseSimultaneously(Type $right, callable $cb): Type
643: {
644: if ($this->subtractedType === null) {
645: return $this;
646: }
647:
648: return new self($this->classReflection);
649: }
650:
651: public function subtract(Type $type): Type
652: {
653: if ($this->subtractedType !== null) {
654: $type = TypeCombinator::union($this->subtractedType, $type);
655: }
656:
657: return $this->changeSubtractedType($type);
658: }
659:
660: public function getTypeWithoutSubtractedType(): Type
661: {
662: return $this->changeSubtractedType(null);
663: }
664:
665: public function changeSubtractedType(?Type $subtractedType): Type
666: {
667: if ($subtractedType !== null) {
668: $classReflection = $this->getClassReflection();
669: if ($classReflection->getAllowedSubTypes() !== null) {
670: $objectType = $this->getStaticObjectType()->changeSubtractedType($subtractedType);
671: if ($objectType instanceof NeverType) {
672: return $objectType;
673: }
674:
675: if ($objectType instanceof ObjectType && $objectType->getSubtractedType() !== null) {
676: return new self($classReflection, $objectType->getSubtractedType());
677: }
678:
679: return TypeCombinator::intersect($this, $objectType);
680: }
681: }
682:
683: return new self($this->classReflection, $subtractedType);
684: }
685:
686: public function getSubtractedType(): ?Type
687: {
688: return $this->subtractedType;
689: }
690:
691: public function tryRemove(Type $typeToRemove): ?Type
692: {
693: if ($this->getStaticObjectType()->isSuperTypeOf($typeToRemove)->yes()) {
694: return $this->subtract($typeToRemove);
695: }
696:
697: return null;
698: }
699:
700: public function exponentiate(Type $exponent): Type
701: {
702: return $this->getStaticObjectType()->exponentiate($exponent);
703: }
704:
705: public function getFiniteTypes(): array
706: {
707: return $this->getStaticObjectType()->getFiniteTypes();
708: }
709:
710: public function toPhpDocNode(): TypeNode
711: {
712: return new IdentifierTypeNode('static');
713: }
714:
715: /**
716: * @param mixed[] $properties
717: */
718: public static function __set_state(array $properties): Type
719: {
720: $reflectionProvider = ReflectionProviderStaticAccessor::getInstance();
721: if ($reflectionProvider->hasClass($properties['baseClass'])) {
722: return new self($reflectionProvider->getClass($properties['baseClass']), $properties['subtractedType'] ?? null);
723: }
724:
725: return new ErrorType();
726: }
727:
728: }
729: