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\Type\AcceptsResult;
10: use PHPStan\Type\ClassStringType;
11: use PHPStan\Type\CompoundType;
12: use PHPStan\Type\Constant\ConstantStringType;
13: use PHPStan\Type\IntersectionType;
14: use PHPStan\Type\IsSuperTypeOfResult;
15: use PHPStan\Type\MixedType;
16: use PHPStan\Type\NeverType;
17: use PHPStan\Type\ObjectType;
18: use PHPStan\Type\ObjectWithoutClassType;
19: use PHPStan\Type\StaticType;
20: use PHPStan\Type\StringType;
21: use PHPStan\Type\Type;
22: use PHPStan\Type\TypeCombinator;
23: use PHPStan\Type\UnionType;
24: use PHPStan\Type\VerbosityLevel;
25: use function count;
26: use function sprintf;
27:
28: /** @api */
29: class GenericClassStringType extends ClassStringType
30: {
31:
32: /** @api */
33: public function __construct(private Type $type)
34: {
35: parent::__construct();
36: }
37:
38: public function getReferencedClasses(): array
39: {
40: return $this->type->getReferencedClasses();
41: }
42:
43: public function getGenericType(): Type
44: {
45: return $this->type;
46: }
47:
48: public function getClassStringObjectType(): Type
49: {
50: return $this->getGenericType();
51: }
52:
53: public function getObjectTypeOrClassStringObjectType(): Type
54: {
55: return $this->getClassStringObjectType();
56: }
57:
58: public function describe(VerbosityLevel $level): string
59: {
60: return sprintf('%s<%s>', parent::describe($level), $this->type->describe($level));
61: }
62:
63: public function accepts(Type $type, bool $strictTypes): AcceptsResult
64: {
65: if ($type instanceof CompoundType) {
66: return $type->isAcceptedBy($this, $strictTypes);
67: }
68:
69: if ($type instanceof ConstantStringType) {
70: if (!$type->isClassString()->yes()) {
71: return AcceptsResult::createNo();
72: }
73:
74: $objectType = new ObjectType($type->getValue());
75: } elseif ($type instanceof self) {
76: $objectType = $type->type;
77: } elseif ($type instanceof ClassStringType) {
78: $objectType = new ObjectWithoutClassType();
79: } elseif ($type instanceof StringType) {
80: return AcceptsResult::createMaybe();
81: } else {
82: return AcceptsResult::createNo();
83: }
84:
85: return $this->type->accepts($objectType, $strictTypes);
86: }
87:
88: public function isSuperTypeOf(Type $type): IsSuperTypeOfResult
89: {
90: if ($type instanceof CompoundType) {
91: return $type->isSubTypeOf($this);
92: }
93:
94: if ($type instanceof ConstantStringType) {
95: $genericType = $this->type;
96: if ($genericType instanceof MixedType) {
97: return IsSuperTypeOfResult::createYes();
98: }
99:
100: if ($genericType instanceof StaticType) {
101: $genericType = $genericType->getStaticObjectType();
102: }
103:
104: // We are transforming constant class-string to ObjectType. But we need to filter out
105: // an uncertainty originating in possible ObjectType's class subtypes.
106: $objectType = new ObjectType($type->getValue());
107:
108: // Do not use TemplateType's isSuperTypeOf handling directly because it takes ObjectType
109: // uncertainty into account.
110: if ($genericType instanceof TemplateType) {
111: $isSuperType = $genericType->getBound()->isSuperTypeOf($objectType);
112: } else {
113: $isSuperType = $genericType->isSuperTypeOf($objectType);
114: }
115:
116: if (!$type->isClassString()->yes()) {
117: $isSuperType = $isSuperType->and(IsSuperTypeOfResult::createMaybe());
118: }
119:
120: return $isSuperType;
121: } elseif ($type instanceof self) {
122: return $this->type->isSuperTypeOf($type->type);
123: } elseif ($type instanceof StringType) {
124: return IsSuperTypeOfResult::createMaybe();
125: }
126:
127: return IsSuperTypeOfResult::createNo();
128: }
129:
130: public function traverse(callable $cb): Type
131: {
132: $newType = $cb($this->type);
133: if ($newType === $this->type) {
134: return $this;
135: }
136:
137: return new self($newType);
138: }
139:
140: public function traverseSimultaneously(Type $right, callable $cb): Type
141: {
142: $newType = $cb($this->type, $right->getClassStringObjectType());
143: if ($newType === $this->type) {
144: return $this;
145: }
146:
147: return new self($newType);
148: }
149:
150: public function inferTemplateTypes(Type $receivedType): TemplateTypeMap
151: {
152: if ($receivedType instanceof UnionType || $receivedType instanceof IntersectionType) {
153: return $receivedType->inferTemplateTypesOn($this);
154: }
155:
156: if ($receivedType instanceof ConstantStringType) {
157: $typeToInfer = new ObjectType($receivedType->getValue());
158: } elseif ($receivedType instanceof self) {
159: $typeToInfer = $receivedType->type;
160: } elseif ($receivedType->isClassString()->yes()) {
161: $typeToInfer = $this->type;
162: if ($typeToInfer instanceof TemplateType) {
163: $typeToInfer = $typeToInfer->getBound();
164: }
165:
166: $typeToInfer = TypeCombinator::intersect($typeToInfer, new ObjectWithoutClassType());
167: } else {
168: return TemplateTypeMap::createEmpty();
169: }
170:
171: return $this->type->inferTemplateTypes($typeToInfer);
172: }
173:
174: public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array
175: {
176: $variance = $positionVariance->compose(TemplateTypeVariance::createCovariant());
177:
178: return $this->type->getReferencedTemplateTypes($variance);
179: }
180:
181: public function equals(Type $type): bool
182: {
183: if (!$type instanceof self) {
184: return false;
185: }
186:
187: if (!parent::equals($type)) {
188: return false;
189: }
190:
191: if (!$this->type->equals($type->type)) {
192: return false;
193: }
194:
195: return true;
196: }
197:
198: public function toPhpDocNode(): TypeNode
199: {
200: return new GenericTypeNode(
201: new IdentifierTypeNode('class-string'),
202: [
203: $this->type->toPhpDocNode(),
204: ],
205: );
206: }
207:
208: public function tryRemove(Type $typeToRemove): ?Type
209: {
210: if ($typeToRemove instanceof ConstantStringType && $typeToRemove->isClassString()->yes()) {
211: $generic = $this->getGenericType();
212:
213: $genericObjectClassNames = $generic->getObjectClassNames();
214: if (count($genericObjectClassNames) === 1) {
215: $classReflection = ReflectionProviderStaticAccessor::getInstance()->getClass($genericObjectClassNames[0]);
216: if ($classReflection->isFinal() && $genericObjectClassNames[0] === $typeToRemove->getValue()) {
217: return new NeverType();
218: }
219: }
220: }
221:
222: return parent::tryRemove($typeToRemove);
223: }
224:
225: }
226: