1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Type\Generic;
4:
5: use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
6: use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
7: use PHPStan\PhpDocParser\Ast\Type\TypeNode;
8: use PHPStan\Reflection\ReflectionProviderStaticAccessor;
9: use PHPStan\Turbo\ShadowedByTurboExtension;
10: use PHPStan\Type\AcceptsResult;
11: use PHPStan\Type\ClassNameToObjectTypeResult;
12: use PHPStan\Type\ClassStringType;
13: use PHPStan\Type\CompoundType;
14: use PHPStan\Type\Constant\ConstantStringType;
15: use PHPStan\Type\InstanceofDeprecated;
16: use PHPStan\Type\IntersectionType;
17: use PHPStan\Type\IsSuperTypeOfResult;
18: use PHPStan\Type\MixedType;
19: use PHPStan\Type\NeverType;
20: use PHPStan\Type\ObjectType;
21: use PHPStan\Type\ObjectWithoutClassType;
22: use PHPStan\Type\StaticType;
23: use PHPStan\Type\StringType;
24: use PHPStan\Type\Type;
25: use PHPStan\Type\TypeCombinator;
26: use PHPStan\Type\UnionType;
27: use PHPStan\Type\VerbosityLevel;
28: use function count;
29: use function sprintf;
30:
31: /** @api */
32: #[InstanceofDeprecated(insteadUse: 'Type::isClassStringType() and Type::getClassStringObjectType()')]
33: #[ShadowedByTurboExtension(implementation: __DIR__ . '/../../../turbo-ext/src/GenericClassStringType.cpp')]
34: class GenericClassStringType extends ClassStringType
35: {
36:
37: /** @api */
38: public function __construct(private Type $type)
39: {
40: parent::__construct();
41: }
42:
43: public function getReferencedClasses(): array
44: {
45: return $this->type->getReferencedClasses();
46: }
47:
48: public function getGenericType(): Type
49: {
50: return $this->type;
51: }
52:
53: public function toObjectTypeForInstanceofCheck(): ClassNameToObjectTypeResult
54: {
55: // `class-string<X>` narrows to `X` for the comparison target, but
56: // the actual runtime class can be any subclass of `X` — keep
57: // uncertainty so the caller falls back to `BooleanType` instead
58: // of a definite yes when `$x instanceof Y` and `Y === X`.
59: return new ClassNameToObjectTypeResult($this->getGenericType(), true);
60: }
61:
62: public function toObjectTypeForIsACheck(Type $objectOrClassType, bool $allowString, bool $allowSameClass): ClassNameToObjectTypeResult
63: {
64: if ($allowString) {
65: return new ClassNameToObjectTypeResult(
66: TypeCombinator::union($this->getGenericType(), $this),
67: false,
68: );
69: }
70:
71: return new ClassNameToObjectTypeResult($this->getGenericType(), false);
72: }
73:
74: public function getClassStringObjectType(): Type
75: {
76: return $this->getGenericType();
77: }
78:
79: public function getObjectTypeOrClassStringObjectType(): Type
80: {
81: return $this->getClassStringObjectType();
82: }
83:
84: public function describe(VerbosityLevel $level): string
85: {
86: return sprintf('%s<%s>', parent::describe($level), $this->type->describe($level));
87: }
88:
89: public function accepts(Type $type, bool $strictTypes): AcceptsResult
90: {
91: if ($type instanceof CompoundType) {
92: return $type->isAcceptedBy($this, $strictTypes);
93: }
94:
95: if ($type instanceof ConstantStringType) {
96: if (!$type->isClassString()->yes()) {
97: return AcceptsResult::createNo();
98: }
99:
100: $objectType = new ObjectType($type->getValue());
101: } elseif ($type instanceof self) {
102: $objectType = $type->type;
103: } elseif ($type instanceof ClassStringType) {
104: $objectType = new ObjectWithoutClassType();
105: } elseif ($type instanceof StringType) {
106: return AcceptsResult::createMaybe();
107: } else {
108: return AcceptsResult::createNo();
109: }
110:
111: return $this->type->accepts($objectType, $strictTypes);
112: }
113:
114: public function isSuperTypeOf(Type $type): IsSuperTypeOfResult
115: {
116: if ($type instanceof CompoundType) {
117: return $type->isSubTypeOf($this);
118: }
119:
120: if ($type instanceof ConstantStringType) {
121: $genericType = $this->type;
122: if ($genericType instanceof MixedType) {
123: return IsSuperTypeOfResult::createYes();
124: }
125:
126: if ($genericType instanceof StaticType) {
127: $genericType = $genericType->getStaticObjectType();
128: }
129:
130: // We are transforming constant class-string to ObjectType. But we need to filter out
131: // an uncertainty originating in possible ObjectType's class subtypes.
132: $objectType = new ObjectType($type->getValue());
133:
134: // Do not use TemplateType's isSuperTypeOf handling directly because it takes ObjectType
135: // uncertainty into account.
136: if ($genericType instanceof TemplateType) {
137: $isSuperType = $genericType->getBound()->isSuperTypeOf($objectType);
138: } else {
139: $isSuperType = $genericType->isSuperTypeOf($objectType);
140: }
141:
142: if (!$type->isClassString()->yes()) {
143: $isSuperType = $isSuperType->and(IsSuperTypeOfResult::createMaybe());
144: }
145:
146: return $isSuperType;
147: } elseif ($type instanceof self) {
148: return $this->type->isSuperTypeOf($type->type);
149: } elseif ($type instanceof ClassStringType) {
150: return $this->type->isSuperTypeOf(new ObjectWithoutClassType());
151: } elseif ($type instanceof StringType) {
152: return IsSuperTypeOfResult::createMaybe();
153: }
154:
155: return IsSuperTypeOfResult::createNo();
156: }
157:
158: public function traverse(callable $cb): Type
159: {
160: $newType = $cb($this->type);
161: if ($newType === $this->type) {
162: return $this;
163: }
164:
165: return new self($newType);
166: }
167:
168: public function traverseSimultaneously(Type $right, callable $cb): Type
169: {
170: $newType = $cb($this->type, $right->getClassStringObjectType());
171: if ($newType === $this->type) {
172: return $this;
173: }
174:
175: return new self($newType);
176: }
177:
178: public function inferTemplateTypes(Type $receivedType): TemplateTypeMap
179: {
180: if ($receivedType instanceof UnionType || $receivedType instanceof IntersectionType) {
181: return $receivedType->inferTemplateTypesOn($this);
182: }
183:
184: if ($receivedType instanceof ConstantStringType) {
185: $typeToInfer = new ObjectType($receivedType->getValue());
186: } elseif ($receivedType instanceof self) {
187: $typeToInfer = $receivedType->type;
188: } elseif ($receivedType->isClassString()->yes()) {
189: $typeToInfer = $this->type;
190: if ($typeToInfer instanceof TemplateType) {
191: $typeToInfer = $typeToInfer->getBound();
192: }
193:
194: $typeToInfer = TypeCombinator::intersect($typeToInfer, new ObjectWithoutClassType());
195: } else {
196: return TemplateTypeMap::createEmpty();
197: }
198:
199: return $this->type->inferTemplateTypes($typeToInfer);
200: }
201:
202: public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array
203: {
204: $variance = $positionVariance->compose(TemplateTypeVariance::createCovariant());
205:
206: return $this->type->getReferencedTemplateTypes($variance);
207: }
208:
209: public function equals(Type $type): bool
210: {
211: if (!$type instanceof self) {
212: return false;
213: }
214:
215: if (!parent::equals($type)) {
216: return false;
217: }
218:
219: if (!$this->type->equals($type->type)) {
220: return false;
221: }
222:
223: return true;
224: }
225:
226: public function toPhpDocNode(): TypeNode
227: {
228: return new GenericTypeNode(
229: new IdentifierTypeNode('class-string'),
230: [
231: $this->type->toPhpDocNode(),
232: ],
233: );
234: }
235:
236: public function tryRemove(Type $typeToRemove): ?Type
237: {
238: if ($typeToRemove instanceof ConstantStringType && $typeToRemove->isClassString()->yes()) {
239: $generic = $this->getGenericType();
240:
241: $genericObjectClassNames = $generic->getObjectClassNames();
242: $reflectionProvider = ReflectionProviderStaticAccessor::getInstance();
243:
244: if (count($genericObjectClassNames) === 1) {
245: if ($reflectionProvider->hasClass($genericObjectClassNames[0])) {
246: $classReflection = $reflectionProvider->getClass($genericObjectClassNames[0]);
247: if ($classReflection->isFinal() && $genericObjectClassNames[0] === $typeToRemove->getValue()) {
248: return new NeverType();
249: }
250:
251: if ($classReflection->getAllowedSubTypes() !== null) {
252: $objectTypeToRemove = new ObjectType($typeToRemove->getValue());
253: $remainingType = TypeCombinator::remove($generic, $objectTypeToRemove);
254: if ($remainingType instanceof NeverType) {
255: return new NeverType();
256: }
257:
258: if (!$remainingType->equals($generic)) {
259: return new self($remainingType);
260: }
261: }
262: }
263: } elseif (count($genericObjectClassNames) > 1) {
264: $objectTypeToRemove = new ObjectType($typeToRemove->getValue());
265: if ($reflectionProvider->hasClass($typeToRemove->getValue())) {
266: $classReflection = $reflectionProvider->getClass($typeToRemove->getValue());
267: if ($classReflection->isFinal()) {
268: $remainingType = TypeCombinator::remove($generic, $objectTypeToRemove);
269: if ($remainingType instanceof NeverType) {
270: return new NeverType();
271: }
272:
273: return new self($remainingType);
274: }
275: }
276: }
277: }
278:
279: return parent::tryRemove($typeToRemove);
280: }
281:
282: public function hasTemplateOrLateResolvableType(): bool
283: {
284: return $this->type->hasTemplateOrLateResolvableType();
285: }
286:
287: }
288: