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\ExtendedMethodReflection;
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\Generic\GenericObjectType;
18: use PHPStan\Type\Generic\TemplateTypeHelper;
19: use PHPStan\Type\Traits\NonGeneralizableTypeTrait;
20: use PHPStan\Type\Traits\NonGenericTypeTrait;
21: use PHPStan\Type\Traits\UndecidedComparisonTypeTrait;
22: use function get_class;
23: use function sprintf;
24:
25: /** @api */
26: class StaticType implements TypeWithClassName, SubtractableType
27: {
28:
29: use NonGenericTypeTrait;
30: use UndecidedComparisonTypeTrait;
31: use NonGeneralizableTypeTrait;
32:
33: private ?Type $subtractedType;
34:
35: private ?ObjectType $staticObjectType = null;
36:
37: private string $baseClass;
38:
39: /**
40: * @api
41: */
42: public function __construct(
43: private ClassReflection $classReflection,
44: ?Type $subtractedType = null,
45: )
46: {
47: if ($subtractedType instanceof NeverType) {
48: $subtractedType = null;
49: }
50:
51: $this->subtractedType = $subtractedType;
52: $this->baseClass = $classReflection->getName();
53: }
54:
55: public function getClassName(): string
56: {
57: return $this->baseClass;
58: }
59:
60: public function getClassReflection(): ClassReflection
61: {
62: return $this->classReflection;
63: }
64:
65: public function getAncestorWithClassName(string $className): ?TypeWithClassName
66: {
67: $ancestor = $this->getStaticObjectType()->getAncestorWithClassName($className);
68: if ($ancestor === null) {
69: return null;
70: }
71:
72: $classReflection = $ancestor->getClassReflection();
73: if ($classReflection !== null) {
74: return $this->changeBaseClass($classReflection);
75: }
76:
77: return null;
78: }
79:
80: public function getStaticObjectType(): ObjectType
81: {
82: if ($this->staticObjectType === null) {
83: if ($this->classReflection->isGeneric()) {
84: $typeMap = $this->classReflection->getActiveTemplateTypeMap()->map(static fn (string $name, Type $type): Type => TemplateTypeHelper::toArgument($type));
85: return $this->staticObjectType = new GenericObjectType(
86: $this->classReflection->getName(),
87: $this->classReflection->typeMapToList($typeMap),
88: $this->subtractedType,
89: );
90: }
91:
92: return $this->staticObjectType = new ObjectType($this->classReflection->getName(), $this->subtractedType, $this->classReflection);
93: }
94:
95: return $this->staticObjectType;
96: }
97:
98: /**
99: * @return string[]
100: */
101: public function getReferencedClasses(): array
102: {
103: return $this->getStaticObjectType()->getReferencedClasses();
104: }
105:
106: public function getArrays(): array
107: {
108: return $this->getStaticObjectType()->getArrays();
109: }
110:
111: public function getConstantArrays(): array
112: {
113: return $this->getStaticObjectType()->getConstantArrays();
114: }
115:
116: public function getConstantStrings(): array
117: {
118: return $this->getStaticObjectType()->getConstantStrings();
119: }
120:
121: public function accepts(Type $type, bool $strictTypes): TrinaryLogic
122: {
123: if ($type instanceof CompoundType) {
124: return $type->isAcceptedBy($this, $strictTypes);
125: }
126:
127: if (!$type instanceof static) {
128: return TrinaryLogic::createNo();
129: }
130:
131: return $this->getStaticObjectType()->accepts($type->getStaticObjectType(), $strictTypes);
132: }
133:
134: public function isSuperTypeOf(Type $type): TrinaryLogic
135: {
136: if ($type instanceof ObjectType) {
137: $classReflection = $type->getClassReflection();
138: if ($classReflection !== null && $classReflection->isFinal()) {
139: $type = new StaticType($classReflection, $type->getSubtractedType());
140: }
141: }
142:
143: if ($type instanceof self) {
144: return $this->getStaticObjectType()->isSuperTypeOf($type);
145: }
146:
147: if ($type instanceof ObjectWithoutClassType) {
148: return TrinaryLogic::createMaybe();
149: }
150:
151: if ($type instanceof ObjectType) {
152: return $this->getStaticObjectType()->isSuperTypeOf($type)->and(TrinaryLogic::createMaybe());
153: }
154:
155: if ($type instanceof CompoundType) {
156: return $type->isSubTypeOf($this);
157: }
158:
159: return TrinaryLogic::createNo();
160: }
161:
162: public function equals(Type $type): bool
163: {
164: if (get_class($type) !== static::class) {
165: return false;
166: }
167:
168: /** @var StaticType $type */
169: $type = $type;
170: return $this->getStaticObjectType()->equals($type->getStaticObjectType());
171: }
172:
173: public function describe(VerbosityLevel $level): string
174: {
175: return sprintf('static(%s)', $this->getStaticObjectType()->describe($level));
176: }
177:
178: public function canAccessProperties(): TrinaryLogic
179: {
180: return $this->getStaticObjectType()->canAccessProperties();
181: }
182:
183: public function hasProperty(string $propertyName): TrinaryLogic
184: {
185: return $this->getStaticObjectType()->hasProperty($propertyName);
186: }
187:
188: public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection
189: {
190: return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty();
191: }
192:
193: public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
194: {
195: $staticObject = $this->getStaticObjectType();
196: $nakedProperty = $staticObject->getUnresolvedPropertyPrototype($propertyName, $scope)->getNakedProperty();
197:
198: $ancestor = $this->getAncestorWithClassName($nakedProperty->getDeclaringClass()->getName());
199: $classReflection = null;
200: if ($ancestor !== null) {
201: $classReflection = $ancestor->getClassReflection();
202: }
203: if ($classReflection === null) {
204: $classReflection = $nakedProperty->getDeclaringClass();
205: }
206:
207: return new CallbackUnresolvedPropertyPrototypeReflection(
208: $nakedProperty,
209: $classReflection,
210: false,
211: fn (Type $type): Type => $this->transformStaticType($type, $scope),
212: );
213: }
214:
215: public function canCallMethods(): TrinaryLogic
216: {
217: return $this->getStaticObjectType()->canCallMethods();
218: }
219:
220: public function hasMethod(string $methodName): TrinaryLogic
221: {
222: return $this->getStaticObjectType()->hasMethod($methodName);
223: }
224:
225: public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): ExtendedMethodReflection
226: {
227: return $this->getUnresolvedMethodPrototype($methodName, $scope)->getTransformedMethod();
228: }
229:
230: public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection
231: {
232: $staticObject = $this->getStaticObjectType();
233: $nakedMethod = $staticObject->getUnresolvedMethodPrototype($methodName, $scope)->getNakedMethod();
234:
235: $ancestor = $this->getAncestorWithClassName($nakedMethod->getDeclaringClass()->getName());
236: $classReflection = null;
237: if ($ancestor !== null) {
238: $classReflection = $ancestor->getClassReflection();
239: }
240: if ($classReflection === null) {
241: $classReflection = $nakedMethod->getDeclaringClass();
242: }
243:
244: return new CallbackUnresolvedMethodPrototypeReflection(
245: $nakedMethod,
246: $classReflection,
247: false,
248: fn (Type $type): Type => $this->transformStaticType($type, $scope),
249: );
250: }
251:
252: private function transformStaticType(Type $type, ClassMemberAccessAnswerer $scope): Type
253: {
254: return TypeTraverser::map($type, function (Type $type, callable $traverse) use ($scope): Type {
255: if ($type instanceof StaticType) {
256: $classReflection = $this->classReflection;
257: $isFinal = false;
258: if ($scope->isInClass()) {
259: $classReflection = $scope->getClassReflection();
260: $isFinal = $classReflection->isFinal();
261: }
262: $type = $type->changeBaseClass($classReflection);
263: if (!$isFinal) {
264: return $type;
265: }
266:
267: return $type->getStaticObjectType();
268: }
269:
270: return $traverse($type);
271: });
272: }
273:
274: public function canAccessConstants(): TrinaryLogic
275: {
276: return $this->getStaticObjectType()->canAccessConstants();
277: }
278:
279: public function hasConstant(string $constantName): TrinaryLogic
280: {
281: return $this->getStaticObjectType()->hasConstant($constantName);
282: }
283:
284: public function getConstant(string $constantName): ConstantReflection
285: {
286: return $this->getStaticObjectType()->getConstant($constantName);
287: }
288:
289: public function changeBaseClass(ClassReflection $classReflection): self
290: {
291: return new self($classReflection, $this->subtractedType);
292: }
293:
294: public function isIterable(): TrinaryLogic
295: {
296: return $this->getStaticObjectType()->isIterable();
297: }
298:
299: public function isIterableAtLeastOnce(): TrinaryLogic
300: {
301: return $this->getStaticObjectType()->isIterableAtLeastOnce();
302: }
303:
304: public function getArraySize(): Type
305: {
306: return $this->getStaticObjectType()->getArraySize();
307: }
308:
309: public function getIterableKeyType(): Type
310: {
311: return $this->getStaticObjectType()->getIterableKeyType();
312: }
313:
314: public function getFirstIterableKeyType(): Type
315: {
316: return $this->getStaticObjectType()->getFirstIterableKeyType();
317: }
318:
319: public function getLastIterableKeyType(): Type
320: {
321: return $this->getStaticObjectType()->getLastIterableKeyType();
322: }
323:
324: public function getIterableValueType(): Type
325: {
326: return $this->getStaticObjectType()->getIterableValueType();
327: }
328:
329: public function getFirstIterableValueType(): Type
330: {
331: return $this->getStaticObjectType()->getFirstIterableValueType();
332: }
333:
334: public function getLastIterableValueType(): Type
335: {
336: return $this->getStaticObjectType()->getLastIterableValueType();
337: }
338:
339: public function isOffsetAccessible(): TrinaryLogic
340: {
341: return $this->getStaticObjectType()->isOffsetAccessible();
342: }
343:
344: public function hasOffsetValueType(Type $offsetType): TrinaryLogic
345: {
346: return $this->getStaticObjectType()->hasOffsetValueType($offsetType);
347: }
348:
349: public function getOffsetValueType(Type $offsetType): Type
350: {
351: return $this->getStaticObjectType()->getOffsetValueType($offsetType);
352: }
353:
354: public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type
355: {
356: return $this->getStaticObjectType()->setOffsetValueType($offsetType, $valueType, $unionValues);
357: }
358:
359: public function unsetOffset(Type $offsetType): Type
360: {
361: return $this->getStaticObjectType()->unsetOffset($offsetType);
362: }
363:
364: public function getKeysArray(): Type
365: {
366: return $this->getStaticObjectType()->getKeysArray();
367: }
368:
369: public function getValuesArray(): Type
370: {
371: return $this->getStaticObjectType()->getValuesArray();
372: }
373:
374: public function fillKeysArray(Type $valueType): Type
375: {
376: return $this->getStaticObjectType()->fillKeysArray($valueType);
377: }
378:
379: public function flipArray(): Type
380: {
381: return $this->getStaticObjectType()->flipArray();
382: }
383:
384: public function intersectKeyArray(Type $otherArraysType): Type
385: {
386: return $this->getStaticObjectType()->intersectKeyArray($otherArraysType);
387: }
388:
389: public function popArray(): Type
390: {
391: return $this->getStaticObjectType()->popArray();
392: }
393:
394: public function searchArray(Type $needleType): Type
395: {
396: return $this->getStaticObjectType()->searchArray($needleType);
397: }
398:
399: public function shiftArray(): Type
400: {
401: return $this->getStaticObjectType()->shiftArray();
402: }
403:
404: public function shuffleArray(): Type
405: {
406: return $this->getStaticObjectType()->shuffleArray();
407: }
408:
409: public function isCallable(): TrinaryLogic
410: {
411: return $this->getStaticObjectType()->isCallable();
412: }
413:
414: public function isArray(): TrinaryLogic
415: {
416: return $this->getStaticObjectType()->isArray();
417: }
418:
419: public function isConstantArray(): TrinaryLogic
420: {
421: return $this->getStaticObjectType()->isConstantArray();
422: }
423:
424: public function isOversizedArray(): TrinaryLogic
425: {
426: return $this->getStaticObjectType()->isOversizedArray();
427: }
428:
429: public function isList(): TrinaryLogic
430: {
431: return $this->getStaticObjectType()->isList();
432: }
433:
434: public function isNull(): TrinaryLogic
435: {
436: return $this->getStaticObjectType()->isNull();
437: }
438:
439: public function isTrue(): TrinaryLogic
440: {
441: return $this->getStaticObjectType()->isTrue();
442: }
443:
444: public function isFalse(): TrinaryLogic
445: {
446: return $this->getStaticObjectType()->isFalse();
447: }
448:
449: public function isBoolean(): TrinaryLogic
450: {
451: return $this->getStaticObjectType()->isBoolean();
452: }
453:
454: public function isFloat(): TrinaryLogic
455: {
456: return $this->getStaticObjectType()->isFloat();
457: }
458:
459: public function isInteger(): TrinaryLogic
460: {
461: return $this->getStaticObjectType()->isInteger();
462: }
463:
464: public function isString(): TrinaryLogic
465: {
466: return $this->getStaticObjectType()->isString();
467: }
468:
469: public function isNumericString(): TrinaryLogic
470: {
471: return $this->getStaticObjectType()->isNumericString();
472: }
473:
474: public function isNonEmptyString(): TrinaryLogic
475: {
476: return $this->getStaticObjectType()->isNonEmptyString();
477: }
478:
479: public function isNonFalsyString(): TrinaryLogic
480: {
481: return $this->getStaticObjectType()->isNonFalsyString();
482: }
483:
484: public function isLiteralString(): TrinaryLogic
485: {
486: return $this->getStaticObjectType()->isLiteralString();
487: }
488:
489: public function isClassStringType(): TrinaryLogic
490: {
491: return $this->getStaticObjectType()->isClassStringType();
492: }
493:
494: public function isVoid(): TrinaryLogic
495: {
496: return $this->getStaticObjectType()->isVoid();
497: }
498:
499: public function isScalar(): TrinaryLogic
500: {
501: return $this->getStaticObjectType()->isScalar();
502: }
503:
504: /**
505: * @return ParametersAcceptor[]
506: */
507: public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array
508: {
509: return $this->getStaticObjectType()->getCallableParametersAcceptors($scope);
510: }
511:
512: public function isCloneable(): TrinaryLogic
513: {
514: return TrinaryLogic::createYes();
515: }
516:
517: public function toNumber(): Type
518: {
519: return new ErrorType();
520: }
521:
522: public function toString(): Type
523: {
524: return $this->getStaticObjectType()->toString();
525: }
526:
527: public function toInteger(): Type
528: {
529: return new ErrorType();
530: }
531:
532: public function toFloat(): Type
533: {
534: return new ErrorType();
535: }
536:
537: public function toArray(): Type
538: {
539: return $this->getStaticObjectType()->toArray();
540: }
541:
542: public function toArrayKey(): Type
543: {
544: return $this->getStaticObjectType()->toArrayKey();
545: }
546:
547: public function toBoolean(): BooleanType
548: {
549: return $this->getStaticObjectType()->toBoolean();
550: }
551:
552: public function traverse(callable $cb): Type
553: {
554: $subtractedType = $this->subtractedType !== null ? $cb($this->subtractedType) : null;
555:
556: if ($subtractedType !== $this->subtractedType) {
557: return new self(
558: $this->classReflection,
559: $subtractedType,
560: );
561: }
562:
563: return $this;
564: }
565:
566: public function subtract(Type $type): Type
567: {
568: if ($this->subtractedType !== null) {
569: $type = TypeCombinator::union($this->subtractedType, $type);
570: }
571:
572: return $this->changeSubtractedType($type);
573: }
574:
575: public function getTypeWithoutSubtractedType(): Type
576: {
577: return $this->changeSubtractedType(null);
578: }
579:
580: public function changeSubtractedType(?Type $subtractedType): Type
581: {
582: if ($subtractedType !== null) {
583: $classReflection = $this->getClassReflection();
584: if ($classReflection->getAllowedSubTypes() !== null) {
585: $objectType = $this->getStaticObjectType()->changeSubtractedType($subtractedType);
586: if ($objectType instanceof NeverType) {
587: return $objectType;
588: }
589:
590: if ($objectType instanceof ObjectType && $objectType->getSubtractedType() !== null) {
591: return new self($classReflection, $objectType->getSubtractedType());
592: }
593:
594: return TypeCombinator::intersect($this, $objectType);
595: }
596: }
597:
598: return new self($this->classReflection, $subtractedType);
599: }
600:
601: public function getSubtractedType(): ?Type
602: {
603: return $this->subtractedType;
604: }
605:
606: public function tryRemove(Type $typeToRemove): ?Type
607: {
608: if ($this->getStaticObjectType()->isSuperTypeOf($typeToRemove)->yes()) {
609: return $this->subtract($typeToRemove);
610: }
611:
612: return null;
613: }
614:
615: /**
616: * @param mixed[] $properties
617: */
618: public static function __set_state(array $properties): Type
619: {
620: $reflectionProvider = ReflectionProviderStaticAccessor::getInstance();
621: if ($reflectionProvider->hasClass($properties['baseClass'])) {
622: return new self($reflectionProvider->getClass($properties['baseClass']), $properties['subtractedType'] ?? null);
623: }
624:
625: return new ErrorType();
626: }
627:
628: }
629: