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: | |
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: | |
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: | |
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: | |
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: | |
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: | |
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: | |