1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Type;
4:
5: use PHPStan\Reflection\ClassMemberAccessAnswerer;
6: use PHPStan\Reflection\ClassReflection;
7: use PHPStan\Reflection\ConstantReflection;
8: use PHPStan\Reflection\MethodReflection;
9: use PHPStan\Reflection\ParametersAcceptor;
10: use PHPStan\Reflection\PropertyReflection;
11: use PHPStan\Reflection\ReflectionProviderStaticAccessor;
12: use PHPStan\Reflection\Type\CallbackUnresolvedMethodPrototypeReflection;
13: use PHPStan\Reflection\Type\CallbackUnresolvedPropertyPrototypeReflection;
14: use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection;
15: use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection;
16: use PHPStan\TrinaryLogic;
17: use PHPStan\Type\Enum\EnumCaseObjectType;
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 accepts(Type $type, bool $strictTypes): TrinaryLogic
108: {
109: if ($type instanceof CompoundType) {
110: return $type->isAcceptedBy($this, $strictTypes);
111: }
112:
113: if (!$type instanceof static) {
114: return TrinaryLogic::createNo();
115: }
116:
117: return $this->getStaticObjectType()->accepts($type->getStaticObjectType(), $strictTypes);
118: }
119:
120: public function isSuperTypeOf(Type $type): TrinaryLogic
121: {
122: if ($type instanceof ObjectType) {
123: $classReflection = $type->getClassReflection();
124: if ($classReflection !== null && $classReflection->isFinal()) {
125: $type = new StaticType($classReflection, $type->getSubtractedType());
126: }
127: }
128:
129: if ($type instanceof self) {
130: return $this->getStaticObjectType()->isSuperTypeOf($type);
131: }
132:
133: if ($type instanceof ObjectWithoutClassType) {
134: return TrinaryLogic::createMaybe();
135: }
136:
137: if ($type instanceof ObjectType) {
138: return $this->getStaticObjectType()->isSuperTypeOf($type)->and(TrinaryLogic::createMaybe());
139: }
140:
141: if ($type instanceof CompoundType) {
142: return $type->isSubTypeOf($this);
143: }
144:
145: return TrinaryLogic::createNo();
146: }
147:
148: public function equals(Type $type): bool
149: {
150: if (get_class($type) !== static::class) {
151: return false;
152: }
153:
154: /** @var StaticType $type */
155: $type = $type;
156: return $this->getStaticObjectType()->equals($type->getStaticObjectType());
157: }
158:
159: public function describe(VerbosityLevel $level): string
160: {
161: return sprintf('static(%s)', $this->getStaticObjectType()->describe($level));
162: }
163:
164: public function canAccessProperties(): TrinaryLogic
165: {
166: return $this->getStaticObjectType()->canAccessProperties();
167: }
168:
169: public function hasProperty(string $propertyName): TrinaryLogic
170: {
171: return $this->getStaticObjectType()->hasProperty($propertyName);
172: }
173:
174: public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection
175: {
176: return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty();
177: }
178:
179: public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
180: {
181: $staticObject = $this->getStaticObjectType();
182: $nakedProperty = $staticObject->getUnresolvedPropertyPrototype($propertyName, $scope)->getNakedProperty();
183:
184: $ancestor = $this->getAncestorWithClassName($nakedProperty->getDeclaringClass()->getName());
185: $classReflection = null;
186: if ($ancestor !== null) {
187: $classReflection = $ancestor->getClassReflection();
188: }
189: if ($classReflection === null) {
190: $classReflection = $nakedProperty->getDeclaringClass();
191: }
192:
193: return new CallbackUnresolvedPropertyPrototypeReflection(
194: $nakedProperty,
195: $classReflection,
196: false,
197: fn (Type $type): Type => $this->transformStaticType($type, $scope),
198: );
199: }
200:
201: public function canCallMethods(): TrinaryLogic
202: {
203: return $this->getStaticObjectType()->canCallMethods();
204: }
205:
206: public function hasMethod(string $methodName): TrinaryLogic
207: {
208: return $this->getStaticObjectType()->hasMethod($methodName);
209: }
210:
211: public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): MethodReflection
212: {
213: return $this->getUnresolvedMethodPrototype($methodName, $scope)->getTransformedMethod();
214: }
215:
216: public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection
217: {
218: $staticObject = $this->getStaticObjectType();
219: $nakedMethod = $staticObject->getUnresolvedMethodPrototype($methodName, $scope)->getNakedMethod();
220:
221: $ancestor = $this->getAncestorWithClassName($nakedMethod->getDeclaringClass()->getName());
222: $classReflection = null;
223: if ($ancestor !== null) {
224: $classReflection = $ancestor->getClassReflection();
225: }
226: if ($classReflection === null) {
227: $classReflection = $nakedMethod->getDeclaringClass();
228: }
229:
230: return new CallbackUnresolvedMethodPrototypeReflection(
231: $nakedMethod,
232: $classReflection,
233: false,
234: fn (Type $type): Type => $this->transformStaticType($type, $scope),
235: );
236: }
237:
238: private function transformStaticType(Type $type, ClassMemberAccessAnswerer $scope): Type
239: {
240: return TypeTraverser::map($type, function (Type $type, callable $traverse) use ($scope): Type {
241: if ($type instanceof StaticType) {
242: $classReflection = $this->classReflection;
243: $isFinal = false;
244: if ($scope->isInClass()) {
245: $classReflection = $scope->getClassReflection();
246: $isFinal = $classReflection->isFinal();
247: }
248: $type = $type->changeBaseClass($classReflection);
249: if (!$isFinal) {
250: return $type;
251: }
252:
253: return $type->getStaticObjectType();
254: }
255:
256: return $traverse($type);
257: });
258: }
259:
260: public function canAccessConstants(): TrinaryLogic
261: {
262: return $this->getStaticObjectType()->canAccessConstants();
263: }
264:
265: public function hasConstant(string $constantName): TrinaryLogic
266: {
267: return $this->getStaticObjectType()->hasConstant($constantName);
268: }
269:
270: public function getConstant(string $constantName): ConstantReflection
271: {
272: return $this->getStaticObjectType()->getConstant($constantName);
273: }
274:
275: public function changeBaseClass(ClassReflection $classReflection): self
276: {
277: return new self($classReflection, $this->subtractedType);
278: }
279:
280: public function isIterable(): TrinaryLogic
281: {
282: return $this->getStaticObjectType()->isIterable();
283: }
284:
285: public function isIterableAtLeastOnce(): TrinaryLogic
286: {
287: return $this->getStaticObjectType()->isIterableAtLeastOnce();
288: }
289:
290: public function getIterableKeyType(): Type
291: {
292: return $this->getStaticObjectType()->getIterableKeyType();
293: }
294:
295: public function getIterableValueType(): Type
296: {
297: return $this->getStaticObjectType()->getIterableValueType();
298: }
299:
300: public function isOffsetAccessible(): TrinaryLogic
301: {
302: return $this->getStaticObjectType()->isOffsetAccessible();
303: }
304:
305: public function hasOffsetValueType(Type $offsetType): TrinaryLogic
306: {
307: return $this->getStaticObjectType()->hasOffsetValueType($offsetType);
308: }
309:
310: public function getOffsetValueType(Type $offsetType): Type
311: {
312: return $this->getStaticObjectType()->getOffsetValueType($offsetType);
313: }
314:
315: public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type
316: {
317: return $this->getStaticObjectType()->setOffsetValueType($offsetType, $valueType, $unionValues);
318: }
319:
320: public function unsetOffset(Type $offsetType): Type
321: {
322: return $this->getStaticObjectType()->unsetOffset($offsetType);
323: }
324:
325: public function isCallable(): TrinaryLogic
326: {
327: return $this->getStaticObjectType()->isCallable();
328: }
329:
330: public function isArray(): TrinaryLogic
331: {
332: return $this->getStaticObjectType()->isArray();
333: }
334:
335: public function isOversizedArray(): TrinaryLogic
336: {
337: return $this->getStaticObjectType()->isOversizedArray();
338: }
339:
340: public function isString(): TrinaryLogic
341: {
342: return $this->getStaticObjectType()->isString();
343: }
344:
345: public function isNumericString(): TrinaryLogic
346: {
347: return $this->getStaticObjectType()->isNumericString();
348: }
349:
350: public function isNonEmptyString(): TrinaryLogic
351: {
352: return $this->getStaticObjectType()->isNonEmptyString();
353: }
354:
355: public function isNonFalsyString(): TrinaryLogic
356: {
357: return $this->getStaticObjectType()->isNonFalsyString();
358: }
359:
360: public function isLiteralString(): TrinaryLogic
361: {
362: return $this->getStaticObjectType()->isLiteralString();
363: }
364:
365: /**
366: * @return ParametersAcceptor[]
367: */
368: public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array
369: {
370: return $this->getStaticObjectType()->getCallableParametersAcceptors($scope);
371: }
372:
373: public function isCloneable(): TrinaryLogic
374: {
375: return TrinaryLogic::createYes();
376: }
377:
378: public function toNumber(): Type
379: {
380: return new ErrorType();
381: }
382:
383: public function toString(): Type
384: {
385: return $this->getStaticObjectType()->toString();
386: }
387:
388: public function toInteger(): Type
389: {
390: return new ErrorType();
391: }
392:
393: public function toFloat(): Type
394: {
395: return new ErrorType();
396: }
397:
398: public function toArray(): Type
399: {
400: return $this->getStaticObjectType()->toArray();
401: }
402:
403: public function toBoolean(): BooleanType
404: {
405: return $this->getStaticObjectType()->toBoolean();
406: }
407:
408: public function traverse(callable $cb): Type
409: {
410: $subtractedType = $this->subtractedType !== null ? $cb($this->subtractedType) : null;
411:
412: if ($subtractedType !== $this->subtractedType) {
413: return new self(
414: $this->classReflection,
415: $subtractedType,
416: );
417: }
418:
419: return $this;
420: }
421:
422: public function subtract(Type $type): Type
423: {
424: if ($this->subtractedType !== null) {
425: $type = TypeCombinator::union($this->subtractedType, $type);
426: }
427:
428: return $this->changeSubtractedType($type);
429: }
430:
431: public function getTypeWithoutSubtractedType(): Type
432: {
433: return $this->changeSubtractedType(null);
434: }
435:
436: public function changeSubtractedType(?Type $subtractedType): Type
437: {
438: if ($subtractedType !== null) {
439: $classReflection = $this->getClassReflection();
440: if ($classReflection->isEnum()) {
441: $objectType = $this->getStaticObjectType()->changeSubtractedType($subtractedType);
442: if ($objectType instanceof NeverType) {
443: return $objectType;
444: }
445:
446: if ($objectType instanceof EnumCaseObjectType) {
447: return TypeCombinator::intersect($this, $objectType);
448: }
449:
450: if ($objectType instanceof ObjectType) {
451: return new self($classReflection, $objectType->getSubtractedType());
452: }
453:
454: return $this;
455: }
456: }
457:
458: return new self($this->classReflection, $subtractedType);
459: }
460:
461: public function getSubtractedType(): ?Type
462: {
463: return $this->subtractedType;
464: }
465:
466: public function tryRemove(Type $typeToRemove): ?Type
467: {
468: if ($this->getStaticObjectType()->isSuperTypeOf($typeToRemove)->yes()) {
469: return $this->subtract($typeToRemove);
470: }
471:
472: return null;
473: }
474:
475: /**
476: * @param mixed[] $properties
477: */
478: public static function __set_state(array $properties): Type
479: {
480: $reflectionProvider = ReflectionProviderStaticAccessor::getInstance();
481: if ($reflectionProvider->hasClass($properties['baseClass'])) {
482: return new self($reflectionProvider->getClass($properties['baseClass']), $properties['subtractedType'] ?? null);
483: }
484:
485: return new ErrorType();
486: }
487:
488: }
489: