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