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