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