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\Generic\GenericObjectType;
19: use PHPStan\Type\Generic\TemplateTypeHelper;
20: use PHPStan\Type\Traits\NonGeneralizableTypeTrait;
21: use PHPStan\Type\Traits\NonGenericTypeTrait;
22: use PHPStan\Type\Traits\UndecidedComparisonTypeTrait;
23: use function get_class;
24: use function sprintf;
25:
26: /** @api */
27: class StaticType implements TypeWithClassName, SubtractableType
28: {
29:
30: use NonGenericTypeTrait;
31: use UndecidedComparisonTypeTrait;
32: use NonGeneralizableTypeTrait;
33:
34: private ?Type $subtractedType;
35:
36: private ?ObjectType $staticObjectType = null;
37:
38: private string $baseClass;
39:
40: /**
41: * @api
42: */
43: public function __construct(
44: private ClassReflection $classReflection,
45: ?Type $subtractedType = null,
46: )
47: {
48: if ($subtractedType instanceof NeverType) {
49: $subtractedType = null;
50: }
51:
52: $this->subtractedType = $subtractedType;
53: $this->baseClass = $classReflection->getName();
54: }
55:
56: public function getClassName(): string
57: {
58: return $this->baseClass;
59: }
60:
61: public function getClassReflection(): ClassReflection
62: {
63: return $this->classReflection;
64: }
65:
66: public function getAncestorWithClassName(string $className): ?TypeWithClassName
67: {
68: $ancestor = $this->getStaticObjectType()->getAncestorWithClassName($className);
69: if ($ancestor === null) {
70: return null;
71: }
72:
73: $classReflection = $ancestor->getClassReflection();
74: if ($classReflection !== null) {
75: return $this->changeBaseClass($classReflection);
76: }
77:
78: return null;
79: }
80:
81: public function getStaticObjectType(): ObjectType
82: {
83: if ($this->staticObjectType === null) {
84: if ($this->classReflection->isGeneric()) {
85: $typeMap = $this->classReflection->getActiveTemplateTypeMap()->map(static fn (string $name, Type $type): Type => TemplateTypeHelper::toArgument($type));
86: $varianceMap = $this->classReflection->getCallSiteVarianceMap();
87: return $this->staticObjectType = new GenericObjectType(
88: $this->classReflection->getName(),
89: $this->classReflection->typeMapToList($typeMap),
90: $this->subtractedType,
91: variances: $this->classReflection->varianceMapToList($varianceMap),
92: );
93: }
94:
95: return $this->staticObjectType = new ObjectType($this->classReflection->getName(), $this->subtractedType, $this->classReflection);
96: }
97:
98: return $this->staticObjectType;
99: }
100:
101: public function getReferencedClasses(): array
102: {
103: return $this->getStaticObjectType()->getReferencedClasses();
104: }
105:
106: public function getObjectClassNames(): array
107: {
108: return $this->getStaticObjectType()->getObjectClassNames();
109: }
110:
111: public function getObjectClassReflections(): array
112: {
113: return $this->getStaticObjectType()->getObjectClassReflections();
114: }
115:
116: public function getArrays(): array
117: {
118: return $this->getStaticObjectType()->getArrays();
119: }
120:
121: public function getConstantArrays(): array
122: {
123: return $this->getStaticObjectType()->getConstantArrays();
124: }
125:
126: public function getConstantStrings(): array
127: {
128: return $this->getStaticObjectType()->getConstantStrings();
129: }
130:
131: public function accepts(Type $type, bool $strictTypes): AcceptsResult
132: {
133: if ($type instanceof CompoundType) {
134: return $type->isAcceptedBy($this, $strictTypes);
135: }
136:
137: if (!$type instanceof static) {
138: return AcceptsResult::createNo();
139: }
140:
141: return $this->getStaticObjectType()->accepts($type->getStaticObjectType(), $strictTypes);
142: }
143:
144: public function isSuperTypeOf(Type $type): IsSuperTypeOfResult
145: {
146: if ($type instanceof self) {
147: return $this->getStaticObjectType()->isSuperTypeOf($type);
148: }
149:
150: if ($type instanceof ObjectWithoutClassType) {
151: return IsSuperTypeOfResult::createMaybe();
152: }
153:
154: if ($type instanceof ObjectType) {
155: $result = $this->getStaticObjectType()->isSuperTypeOf($type);
156: if ($result->yes()) {
157: $classReflection = $type->getClassReflection();
158: if ($classReflection !== null && $classReflection->isFinal()) {
159: return $result;
160: }
161: }
162:
163: return $result->and(IsSuperTypeOfResult::createMaybe());
164: }
165:
166: if ($type instanceof CompoundType) {
167: return $type->isSubTypeOf($this);
168: }
169:
170: return IsSuperTypeOfResult::createNo();
171: }
172:
173: public function equals(Type $type): bool
174: {
175: if (get_class($type) !== static::class) {
176: return false;
177: }
178:
179: return $this->getStaticObjectType()->equals($type->getStaticObjectType());
180: }
181:
182: public function describe(VerbosityLevel $level): string
183: {
184: return sprintf('static(%s)', $this->getStaticObjectType()->describe($level));
185: }
186:
187: public function getTemplateType(string $ancestorClassName, string $templateTypeName): Type
188: {
189: return $this->getStaticObjectType()->getTemplateType($ancestorClassName, $templateTypeName);
190: }
191:
192: public function isObject(): TrinaryLogic
193: {
194: return $this->getStaticObjectType()->isObject();
195: }
196:
197: public function isEnum(): TrinaryLogic
198: {
199: return $this->getStaticObjectType()->isEnum();
200: }
201:
202: public function canAccessProperties(): TrinaryLogic
203: {
204: return $this->getStaticObjectType()->canAccessProperties();
205: }
206:
207: public function hasProperty(string $propertyName): TrinaryLogic
208: {
209: return $this->getStaticObjectType()->hasProperty($propertyName);
210: }
211:
212: public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
213: {
214: return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty();
215: }
216:
217: public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
218: {
219: $staticObject = $this->getStaticObjectType();
220: $nakedProperty = $staticObject->getUnresolvedPropertyPrototype($propertyName, $scope)->getNakedProperty();
221:
222: $ancestor = $this->getAncestorWithClassName($nakedProperty->getDeclaringClass()->getName());
223: $classReflection = null;
224: if ($ancestor !== null) {
225: $classReflection = $ancestor->getClassReflection();
226: }
227: if ($classReflection === null) {
228: $classReflection = $nakedProperty->getDeclaringClass();
229: }
230:
231: return new CallbackUnresolvedPropertyPrototypeReflection(
232: $nakedProperty,
233: $classReflection,
234: false,
235: fn (Type $type): Type => $this->transformStaticType($type, $scope),
236: );
237: }
238:
239: public function canCallMethods(): TrinaryLogic
240: {
241: return $this->getStaticObjectType()->canCallMethods();
242: }
243:
244: public function hasMethod(string $methodName): TrinaryLogic
245: {
246: return $this->getStaticObjectType()->hasMethod($methodName);
247: }
248:
249: public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): ExtendedMethodReflection
250: {
251: return $this->getUnresolvedMethodPrototype($methodName, $scope)->getTransformedMethod();
252: }
253:
254: public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection
255: {
256: $staticObject = $this->getStaticObjectType();
257: $nakedMethod = $staticObject->getUnresolvedMethodPrototype($methodName, $scope)->getNakedMethod();
258:
259: $ancestor = $this->getAncestorWithClassName($nakedMethod->getDeclaringClass()->getName());
260: $classReflection = null;
261: if ($ancestor !== null) {
262: $classReflection = $ancestor->getClassReflection();
263: }
264: if ($classReflection === null) {
265: $classReflection = $nakedMethod->getDeclaringClass();
266: }
267:
268: return new CallbackUnresolvedMethodPrototypeReflection(
269: $nakedMethod,
270: $classReflection,
271: false,
272: fn (Type $type): Type => $this->transformStaticType($type, $scope),
273: );
274: }
275:
276: private function transformStaticType(Type $type, ClassMemberAccessAnswerer $scope): Type
277: {
278: return TypeTraverser::map($type, function (Type $type, callable $traverse) use ($scope): Type {
279: if ($type instanceof StaticType) {
280: $classReflection = $this->classReflection;
281: $isFinal = false;
282: if ($scope->isInClass()) {
283: $classReflection = $scope->getClassReflection();
284: $isFinal = $classReflection->isFinal();
285: }
286: $type = $type->changeBaseClass($classReflection);
287: if (!$isFinal || $type instanceof ThisType) {
288: return $traverse($type);
289: }
290:
291: return $traverse($type->getStaticObjectType());
292: }
293:
294: return $traverse($type);
295: });
296: }
297:
298: public function canAccessConstants(): TrinaryLogic
299: {
300: return $this->getStaticObjectType()->canAccessConstants();
301: }
302:
303: public function hasConstant(string $constantName): TrinaryLogic
304: {
305: return $this->getStaticObjectType()->hasConstant($constantName);
306: }
307:
308: public function getConstant(string $constantName): ClassConstantReflection
309: {
310: return $this->getStaticObjectType()->getConstant($constantName);
311: }
312:
313: public function changeBaseClass(ClassReflection $classReflection): self
314: {
315: return new self($classReflection, $this->subtractedType);
316: }
317:
318: public function isIterable(): TrinaryLogic
319: {
320: return $this->getStaticObjectType()->isIterable();
321: }
322:
323: public function isIterableAtLeastOnce(): TrinaryLogic
324: {
325: return $this->getStaticObjectType()->isIterableAtLeastOnce();
326: }
327:
328: public function getArraySize(): Type
329: {
330: return $this->getStaticObjectType()->getArraySize();
331: }
332:
333: public function getIterableKeyType(): Type
334: {
335: return $this->getStaticObjectType()->getIterableKeyType();
336: }
337:
338: public function getFirstIterableKeyType(): Type
339: {
340: return $this->getStaticObjectType()->getFirstIterableKeyType();
341: }
342:
343: public function getLastIterableKeyType(): Type
344: {
345: return $this->getStaticObjectType()->getLastIterableKeyType();
346: }
347:
348: public function getIterableValueType(): Type
349: {
350: return $this->getStaticObjectType()->getIterableValueType();
351: }
352:
353: public function getFirstIterableValueType(): Type
354: {
355: return $this->getStaticObjectType()->getFirstIterableValueType();
356: }
357:
358: public function getLastIterableValueType(): Type
359: {
360: return $this->getStaticObjectType()->getLastIterableValueType();
361: }
362:
363: public function isOffsetAccessible(): TrinaryLogic
364: {
365: return $this->getStaticObjectType()->isOffsetAccessible();
366: }
367:
368: public function isOffsetAccessLegal(): TrinaryLogic
369: {
370: return $this->getStaticObjectType()->isOffsetAccessLegal();
371: }
372:
373: public function hasOffsetValueType(Type $offsetType): TrinaryLogic
374: {
375: return $this->getStaticObjectType()->hasOffsetValueType($offsetType);
376: }
377:
378: public function getOffsetValueType(Type $offsetType): Type
379: {
380: return $this->getStaticObjectType()->getOffsetValueType($offsetType);
381: }
382:
383: public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type
384: {
385: return $this->getStaticObjectType()->setOffsetValueType($offsetType, $valueType, $unionValues);
386: }
387:
388: public function setExistingOffsetValueType(Type $offsetType, Type $valueType): Type
389: {
390: return $this->getStaticObjectType()->setExistingOffsetValueType($offsetType, $valueType);
391: }
392:
393: public function unsetOffset(Type $offsetType): Type
394: {
395: return $this->getStaticObjectType()->unsetOffset($offsetType);
396: }
397:
398: public function getKeysArray(): Type
399: {
400: return $this->getStaticObjectType()->getKeysArray();
401: }
402:
403: public function getValuesArray(): Type
404: {
405: return $this->getStaticObjectType()->getValuesArray();
406: }
407:
408: public function chunkArray(Type $lengthType, TrinaryLogic $preserveKeys): Type
409: {
410: return $this->getStaticObjectType()->chunkArray($lengthType, $preserveKeys);
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 reverseArray(TrinaryLogic $preserveKeys): Type
434: {
435: return $this->getStaticObjectType()->reverseArray($preserveKeys);
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 sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $preserveKeys): Type
454: {
455: return $this->getStaticObjectType()->sliceArray($offsetType, $lengthType, $preserveKeys);
456: }
457:
458: public function isCallable(): TrinaryLogic
459: {
460: return $this->getStaticObjectType()->isCallable();
461: }
462:
463: public function getEnumCases(): array
464: {
465: return $this->getStaticObjectType()->getEnumCases();
466: }
467:
468: public function isArray(): TrinaryLogic
469: {
470: return $this->getStaticObjectType()->isArray();
471: }
472:
473: public function isConstantArray(): TrinaryLogic
474: {
475: return $this->getStaticObjectType()->isConstantArray();
476: }
477:
478: public function isOversizedArray(): TrinaryLogic
479: {
480: return $this->getStaticObjectType()->isOversizedArray();
481: }
482:
483: public function isList(): TrinaryLogic
484: {
485: return $this->getStaticObjectType()->isList();
486: }
487:
488: public function isNull(): TrinaryLogic
489: {
490: return $this->getStaticObjectType()->isNull();
491: }
492:
493: public function isConstantValue(): TrinaryLogic
494: {
495: return $this->getStaticObjectType()->isConstantValue();
496: }
497:
498: public function isConstantScalarValue(): TrinaryLogic
499: {
500: return $this->getStaticObjectType()->isConstantScalarValue();
501: }
502:
503: public function getConstantScalarTypes(): array
504: {
505: return $this->getStaticObjectType()->getConstantScalarTypes();
506: }
507:
508: public function getConstantScalarValues(): array
509: {
510: return $this->getStaticObjectType()->getConstantScalarValues();
511: }
512:
513: public function isTrue(): TrinaryLogic
514: {
515: return $this->getStaticObjectType()->isTrue();
516: }
517:
518: public function isFalse(): TrinaryLogic
519: {
520: return $this->getStaticObjectType()->isFalse();
521: }
522:
523: public function isBoolean(): TrinaryLogic
524: {
525: return $this->getStaticObjectType()->isBoolean();
526: }
527:
528: public function isFloat(): TrinaryLogic
529: {
530: return $this->getStaticObjectType()->isFloat();
531: }
532:
533: public function isInteger(): TrinaryLogic
534: {
535: return $this->getStaticObjectType()->isInteger();
536: }
537:
538: public function isString(): TrinaryLogic
539: {
540: return $this->getStaticObjectType()->isString();
541: }
542:
543: public function isNumericString(): TrinaryLogic
544: {
545: return $this->getStaticObjectType()->isNumericString();
546: }
547:
548: public function isNonEmptyString(): TrinaryLogic
549: {
550: return $this->getStaticObjectType()->isNonEmptyString();
551: }
552:
553: public function isNonFalsyString(): TrinaryLogic
554: {
555: return $this->getStaticObjectType()->isNonFalsyString();
556: }
557:
558: public function isLiteralString(): TrinaryLogic
559: {
560: return $this->getStaticObjectType()->isLiteralString();
561: }
562:
563: public function isLowercaseString(): TrinaryLogic
564: {
565: return $this->getStaticObjectType()->isLowercaseString();
566: }
567:
568: public function isUppercaseString(): TrinaryLogic
569: {
570: return $this->getStaticObjectType()->isUppercaseString();
571: }
572:
573: public function isClassString(): TrinaryLogic
574: {
575: return $this->getStaticObjectType()->isClassString();
576: }
577:
578: public function getClassStringObjectType(): Type
579: {
580: return $this->getStaticObjectType()->getClassStringObjectType();
581: }
582:
583: public function getObjectTypeOrClassStringObjectType(): Type
584: {
585: return $this;
586: }
587:
588: public function isVoid(): TrinaryLogic
589: {
590: return $this->getStaticObjectType()->isVoid();
591: }
592:
593: public function isScalar(): TrinaryLogic
594: {
595: return $this->getStaticObjectType()->isScalar();
596: }
597:
598: public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType
599: {
600: return new BooleanType();
601: }
602:
603: public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array
604: {
605: return $this->getStaticObjectType()->getCallableParametersAcceptors($scope);
606: }
607:
608: public function isCloneable(): TrinaryLogic
609: {
610: return TrinaryLogic::createYes();
611: }
612:
613: public function toNumber(): Type
614: {
615: return new ErrorType();
616: }
617:
618: public function toAbsoluteNumber(): Type
619: {
620: return new ErrorType();
621: }
622:
623: public function toString(): Type
624: {
625: return $this->getStaticObjectType()->toString();
626: }
627:
628: public function toInteger(): Type
629: {
630: return new ErrorType();
631: }
632:
633: public function toFloat(): Type
634: {
635: return new ErrorType();
636: }
637:
638: public function toArray(): Type
639: {
640: return $this->getStaticObjectType()->toArray();
641: }
642:
643: public function toArrayKey(): Type
644: {
645: return $this->getStaticObjectType()->toArrayKey();
646: }
647:
648: public function toCoercedArgumentType(bool $strictTypes): Type
649: {
650: return $this->getStaticObjectType()->toCoercedArgumentType($strictTypes);
651: }
652:
653: public function toBoolean(): BooleanType
654: {
655: return $this->getStaticObjectType()->toBoolean();
656: }
657:
658: public function traverse(callable $cb): Type
659: {
660: $subtractedType = $this->subtractedType !== null ? $cb($this->subtractedType) : null;
661:
662: if ($subtractedType !== $this->subtractedType) {
663: return new self(
664: $this->classReflection,
665: $subtractedType,
666: );
667: }
668:
669: return $this;
670: }
671:
672: public function traverseSimultaneously(Type $right, callable $cb): Type
673: {
674: if ($this->subtractedType === null) {
675: return $this;
676: }
677:
678: return new self($this->classReflection);
679: }
680:
681: public function subtract(Type $type): Type
682: {
683: if ($this->subtractedType !== null) {
684: $type = TypeCombinator::union($this->subtractedType, $type);
685: }
686:
687: return $this->changeSubtractedType($type);
688: }
689:
690: public function getTypeWithoutSubtractedType(): Type
691: {
692: return $this->changeSubtractedType(null);
693: }
694:
695: public function changeSubtractedType(?Type $subtractedType): Type
696: {
697: if ($subtractedType !== null) {
698: $classReflection = $this->getClassReflection();
699: if ($classReflection->getAllowedSubTypes() !== null) {
700: $objectType = $this->getStaticObjectType()->changeSubtractedType($subtractedType);
701: if ($objectType instanceof NeverType) {
702: return $objectType;
703: }
704:
705: if ($objectType instanceof ObjectType && $objectType->getSubtractedType() !== null) {
706: return new self($classReflection, $objectType->getSubtractedType());
707: }
708:
709: return TypeCombinator::intersect($this, $objectType);
710: }
711: }
712:
713: return new self($this->classReflection, $subtractedType);
714: }
715:
716: public function getSubtractedType(): ?Type
717: {
718: return $this->subtractedType;
719: }
720:
721: public function tryRemove(Type $typeToRemove): ?Type
722: {
723: if ($this->getStaticObjectType()->isSuperTypeOf($typeToRemove)->yes()) {
724: return $this->subtract($typeToRemove);
725: }
726:
727: return null;
728: }
729:
730: public function exponentiate(Type $exponent): Type
731: {
732: return $this->getStaticObjectType()->exponentiate($exponent);
733: }
734:
735: public function getFiniteTypes(): array
736: {
737: return $this->getStaticObjectType()->getFiniteTypes();
738: }
739:
740: public function toPhpDocNode(): TypeNode
741: {
742: return new IdentifierTypeNode('static');
743: }
744:
745: }
746: