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 isOffsetAccessLegal(): TrinaryLogic
379: {
380: return $this->getStaticObjectType()->isOffsetAccessLegal();
381: }
382:
383: public function hasOffsetValueType(Type $offsetType): TrinaryLogic
384: {
385: return $this->getStaticObjectType()->hasOffsetValueType($offsetType);
386: }
387:
388: public function getOffsetValueType(Type $offsetType): Type
389: {
390: return $this->getStaticObjectType()->getOffsetValueType($offsetType);
391: }
392:
393: public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type
394: {
395: return $this->getStaticObjectType()->setOffsetValueType($offsetType, $valueType, $unionValues);
396: }
397:
398: public function setExistingOffsetValueType(Type $offsetType, Type $valueType): Type
399: {
400: return $this->getStaticObjectType()->setExistingOffsetValueType($offsetType, $valueType);
401: }
402:
403: public function unsetOffset(Type $offsetType): Type
404: {
405: return $this->getStaticObjectType()->unsetOffset($offsetType);
406: }
407:
408: public function getKeysArray(): Type
409: {
410: return $this->getStaticObjectType()->getKeysArray();
411: }
412:
413: public function getValuesArray(): Type
414: {
415: return $this->getStaticObjectType()->getValuesArray();
416: }
417:
418: public function fillKeysArray(Type $valueType): Type
419: {
420: return $this->getStaticObjectType()->fillKeysArray($valueType);
421: }
422:
423: public function flipArray(): Type
424: {
425: return $this->getStaticObjectType()->flipArray();
426: }
427:
428: public function intersectKeyArray(Type $otherArraysType): Type
429: {
430: return $this->getStaticObjectType()->intersectKeyArray($otherArraysType);
431: }
432:
433: public function popArray(): Type
434: {
435: return $this->getStaticObjectType()->popArray();
436: }
437:
438: public function searchArray(Type $needleType): Type
439: {
440: return $this->getStaticObjectType()->searchArray($needleType);
441: }
442:
443: public function shiftArray(): Type
444: {
445: return $this->getStaticObjectType()->shiftArray();
446: }
447:
448: public function shuffleArray(): Type
449: {
450: return $this->getStaticObjectType()->shuffleArray();
451: }
452:
453: public function isCallable(): TrinaryLogic
454: {
455: return $this->getStaticObjectType()->isCallable();
456: }
457:
458: public function getEnumCases(): array
459: {
460: return $this->getStaticObjectType()->getEnumCases();
461: }
462:
463: public function isArray(): TrinaryLogic
464: {
465: return $this->getStaticObjectType()->isArray();
466: }
467:
468: public function isConstantArray(): TrinaryLogic
469: {
470: return $this->getStaticObjectType()->isConstantArray();
471: }
472:
473: public function isOversizedArray(): TrinaryLogic
474: {
475: return $this->getStaticObjectType()->isOversizedArray();
476: }
477:
478: public function isList(): TrinaryLogic
479: {
480: return $this->getStaticObjectType()->isList();
481: }
482:
483: public function isNull(): TrinaryLogic
484: {
485: return $this->getStaticObjectType()->isNull();
486: }
487:
488: public function isConstantValue(): TrinaryLogic
489: {
490: return $this->getStaticObjectType()->isConstantValue();
491: }
492:
493: public function isConstantScalarValue(): TrinaryLogic
494: {
495: return $this->getStaticObjectType()->isConstantScalarValue();
496: }
497:
498: public function getConstantScalarTypes(): array
499: {
500: return $this->getStaticObjectType()->getConstantScalarTypes();
501: }
502:
503: public function getConstantScalarValues(): array
504: {
505: return $this->getStaticObjectType()->getConstantScalarValues();
506: }
507:
508: public function isTrue(): TrinaryLogic
509: {
510: return $this->getStaticObjectType()->isTrue();
511: }
512:
513: public function isFalse(): TrinaryLogic
514: {
515: return $this->getStaticObjectType()->isFalse();
516: }
517:
518: public function isBoolean(): TrinaryLogic
519: {
520: return $this->getStaticObjectType()->isBoolean();
521: }
522:
523: public function isFloat(): TrinaryLogic
524: {
525: return $this->getStaticObjectType()->isFloat();
526: }
527:
528: public function isInteger(): TrinaryLogic
529: {
530: return $this->getStaticObjectType()->isInteger();
531: }
532:
533: public function isString(): TrinaryLogic
534: {
535: return $this->getStaticObjectType()->isString();
536: }
537:
538: public function isNumericString(): TrinaryLogic
539: {
540: return $this->getStaticObjectType()->isNumericString();
541: }
542:
543: public function isNonEmptyString(): TrinaryLogic
544: {
545: return $this->getStaticObjectType()->isNonEmptyString();
546: }
547:
548: public function isNonFalsyString(): TrinaryLogic
549: {
550: return $this->getStaticObjectType()->isNonFalsyString();
551: }
552:
553: public function isLiteralString(): TrinaryLogic
554: {
555: return $this->getStaticObjectType()->isLiteralString();
556: }
557:
558: public function isClassStringType(): TrinaryLogic
559: {
560: return $this->getStaticObjectType()->isClassStringType();
561: }
562:
563: public function getClassStringObjectType(): Type
564: {
565: return $this->getStaticObjectType()->getClassStringObjectType();
566: }
567:
568: public function getObjectTypeOrClassStringObjectType(): Type
569: {
570: return $this;
571: }
572:
573: public function isVoid(): TrinaryLogic
574: {
575: return $this->getStaticObjectType()->isVoid();
576: }
577:
578: public function isScalar(): TrinaryLogic
579: {
580: return $this->getStaticObjectType()->isScalar();
581: }
582:
583: public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType
584: {
585: return new BooleanType();
586: }
587:
588: public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array
589: {
590: return $this->getStaticObjectType()->getCallableParametersAcceptors($scope);
591: }
592:
593: public function isCloneable(): TrinaryLogic
594: {
595: return TrinaryLogic::createYes();
596: }
597:
598: public function toNumber(): Type
599: {
600: return new ErrorType();
601: }
602:
603: public function toString(): Type
604: {
605: return $this->getStaticObjectType()->toString();
606: }
607:
608: public function toInteger(): Type
609: {
610: return new ErrorType();
611: }
612:
613: public function toFloat(): Type
614: {
615: return new ErrorType();
616: }
617:
618: public function toArray(): Type
619: {
620: return $this->getStaticObjectType()->toArray();
621: }
622:
623: public function toArrayKey(): Type
624: {
625: return $this->getStaticObjectType()->toArrayKey();
626: }
627:
628: public function toBoolean(): BooleanType
629: {
630: return $this->getStaticObjectType()->toBoolean();
631: }
632:
633: public function traverse(callable $cb): Type
634: {
635: $subtractedType = $this->subtractedType !== null ? $cb($this->subtractedType) : null;
636:
637: if ($subtractedType !== $this->subtractedType) {
638: return new self(
639: $this->classReflection,
640: $subtractedType,
641: );
642: }
643:
644: return $this;
645: }
646:
647: public function traverseSimultaneously(Type $right, callable $cb): Type
648: {
649: if ($this->subtractedType === null) {
650: return $this;
651: }
652:
653: return new self($this->classReflection);
654: }
655:
656: public function subtract(Type $type): Type
657: {
658: if ($this->subtractedType !== null) {
659: $type = TypeCombinator::union($this->subtractedType, $type);
660: }
661:
662: return $this->changeSubtractedType($type);
663: }
664:
665: public function getTypeWithoutSubtractedType(): Type
666: {
667: return $this->changeSubtractedType(null);
668: }
669:
670: public function changeSubtractedType(?Type $subtractedType): Type
671: {
672: if ($subtractedType !== null) {
673: $classReflection = $this->getClassReflection();
674: if ($classReflection->getAllowedSubTypes() !== null) {
675: $objectType = $this->getStaticObjectType()->changeSubtractedType($subtractedType);
676: if ($objectType instanceof NeverType) {
677: return $objectType;
678: }
679:
680: if ($objectType instanceof ObjectType && $objectType->getSubtractedType() !== null) {
681: return new self($classReflection, $objectType->getSubtractedType());
682: }
683:
684: return TypeCombinator::intersect($this, $objectType);
685: }
686: }
687:
688: return new self($this->classReflection, $subtractedType);
689: }
690:
691: public function getSubtractedType(): ?Type
692: {
693: return $this->subtractedType;
694: }
695:
696: public function tryRemove(Type $typeToRemove): ?Type
697: {
698: if ($this->getStaticObjectType()->isSuperTypeOf($typeToRemove)->yes()) {
699: return $this->subtract($typeToRemove);
700: }
701:
702: return null;
703: }
704:
705: public function exponentiate(Type $exponent): Type
706: {
707: return $this->getStaticObjectType()->exponentiate($exponent);
708: }
709:
710: public function getFiniteTypes(): array
711: {
712: return $this->getStaticObjectType()->getFiniteTypes();
713: }
714:
715: public function toPhpDocNode(): TypeNode
716: {
717: return new IdentifierTypeNode('static');
718: }
719:
720: /**
721: * @param mixed[] $properties
722: */
723: public static function __set_state(array $properties): Type
724: {
725: $reflectionProvider = ReflectionProviderStaticAccessor::getInstance();
726: if ($reflectionProvider->hasClass($properties['baseClass'])) {
727: return new self($reflectionProvider->getClass($properties['baseClass']), $properties['subtractedType'] ?? null);
728: }
729:
730: return new ErrorType();
731: }
732:
733: }
734: