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