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\ClassReflection;
9: use PHPStan\ShouldNotHappenException;
10: use PHPStan\Type\CompoundType;
11: use PHPStan\Type\IsSuperTypeOfResult;
12: use PHPStan\Type\NeverType;
13: use PHPStan\Type\ObjectType;
14: use PHPStan\Type\StaticType;
15: use PHPStan\Type\Type;
16: use PHPStan\Type\TypeCombinator;
17: use PHPStan\Type\TypeWithClassName;
18: use function array_key_exists;
19: use function array_map;
20: use function count;
21:
22: /** @api */
23: class GenericStaticType extends StaticType
24: {
25:
26: private ?ObjectType $staticObjectType = null;
27:
28: /**
29: * @api
30: * @param array<int, Type> $types
31: * @param array<int, TemplateTypeVariance> $variances
32: */
33: public function __construct(
34: private ClassReflection $classReflection,
35: private array $types,
36: private ?Type $subtractedType,
37: private array $variances,
38: )
39: {
40: if (count($this->types) === 0) {
41: throw new ShouldNotHappenException('Cannot create GenericStaticType with zero types.');
42: }
43: parent::__construct($classReflection, $subtractedType);
44: }
45:
46: /**
47: * @return array<int, Type>
48: */
49: public function getTypes(): array
50: {
51: return $this->types;
52: }
53:
54: /** @return array<int, TemplateTypeVariance> */
55: public function getVariances(): array
56: {
57: return $this->variances;
58: }
59:
60: public function getStaticObjectType(): ObjectType
61: {
62: if ($this->staticObjectType === null) {
63: if ($this->classReflection->isGeneric()) {
64: return $this->staticObjectType = new GenericObjectType(
65: $this->classReflection->getName(),
66: $this->types,
67: $this->subtractedType,
68: $this->classReflection,
69: $this->variances,
70: );
71: }
72:
73: return $this->staticObjectType = parent::getStaticObjectType();
74: }
75:
76: return $this->staticObjectType;
77: }
78:
79: public function changeBaseClass(ClassReflection $classReflection): StaticType
80: {
81: if ($classReflection->getName() === $this->getClassName()) {
82: return $this;
83: }
84:
85: if (!$classReflection->isGeneric()) {
86: return new StaticType($classReflection);
87: }
88:
89: $templateTags = $this->getClassReflection()->getTemplateTags();
90: $i = 0;
91: $indexedTypes = [];
92: $indexedVariances = [];
93: foreach ($templateTags as $typeName => $tag) {
94: if (!array_key_exists($i, $this->types)) {
95: break;
96: }
97: if (!array_key_exists($i, $this->variances)) {
98: break;
99: }
100: $indexedTypes[$typeName] = $this->types[$i];
101: $indexedVariances[$typeName] = $this->variances[$i];
102: $i++;
103: }
104:
105: $newType = new GenericObjectType($classReflection->getName(), $classReflection->typeMapToList($classReflection->getTemplateTypeMap()));
106: $ancestorType = $newType->getAncestorWithClassName($this->getClassName());
107: if ($ancestorType === null) {
108: return new self(
109: $classReflection,
110: $classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds()),
111: $this->subtractedType,
112: $classReflection->varianceMapToList($classReflection->getCallSiteVarianceMap()),
113: );
114: }
115:
116: $ancestorClassReflection = $ancestorType->getClassReflection();
117: if ($ancestorClassReflection === null) {
118: return new self(
119: $classReflection,
120: $classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds()),
121: $this->subtractedType,
122: $classReflection->varianceMapToList($classReflection->getCallSiteVarianceMap()),
123: );
124: }
125:
126: $newClassTypes = [];
127: $newClassVariances = [];
128: foreach ($ancestorClassReflection->getActiveTemplateTypeMap()->getTypes() as $typeName => $templateType) {
129: if (!$templateType instanceof TemplateType) {
130: continue;
131: }
132:
133: if (!array_key_exists($typeName, $indexedTypes)) {
134: continue;
135: }
136:
137: $newClassTypes[$templateType->getName()] = $indexedTypes[$typeName];
138: $newClassVariances[$templateType->getName()] = $indexedVariances[$typeName];
139: }
140:
141: return new self($classReflection, $classReflection->typeMapToList(new TemplateTypeMap($newClassTypes)), $this->subtractedType, $classReflection->varianceMapToList(new TemplateTypeVarianceMap($newClassVariances)));
142: }
143:
144: public function isSuperTypeOf(Type $type): IsSuperTypeOfResult
145: {
146: if ($type instanceof CompoundType) {
147: return $type->isSubTypeOf($this);
148: }
149:
150: if ($type instanceof self) {
151: return $this->getStaticObjectType()->isSuperTypeOf($type->getStaticObjectType());
152: }
153:
154: return parent::isSuperTypeOf($type)->and(IsSuperTypeOfResult::createMaybe());
155: }
156:
157: public function traverse(callable $cb): Type
158: {
159: $subtractedType = $this->getSubtractedType() !== null ? $cb($this->getSubtractedType()) : null;
160:
161: $typesChanged = false;
162: $types = [];
163: foreach ($this->types as $type) {
164: $newType = $cb($type);
165: $types[] = $newType;
166: if ($newType === $type) {
167: continue;
168: }
169:
170: $typesChanged = true;
171: }
172:
173: if ($subtractedType !== $this->getSubtractedType() || $typesChanged) {
174: return new self(
175: $this->classReflection,
176: $types,
177: $subtractedType,
178: $this->variances,
179: );
180: }
181:
182: return $this;
183: }
184:
185: public function traverseSimultaneously(Type $right, callable $cb): Type
186: {
187: if (!$right instanceof TypeWithClassName) {
188: return $this;
189: }
190:
191: $ancestor = $right->getAncestorWithClassName($this->getClassName());
192: if (!$ancestor instanceof self) {
193: return $this;
194: }
195:
196: if (count($this->types) !== count($ancestor->types)) {
197: return $this;
198: }
199:
200: $typesChanged = false;
201: $types = [];
202: foreach ($this->types as $i => $leftType) {
203: $rightType = $ancestor->types[$i];
204: $newType = $cb($leftType, $rightType);
205: $types[] = $newType;
206: if ($newType === $leftType) {
207: continue;
208: }
209:
210: $typesChanged = true;
211: }
212:
213: if ($typesChanged) {
214: return new self(
215: $this->classReflection,
216: $types,
217: null,
218: $this->variances,
219: );
220: }
221:
222: return $this;
223: }
224:
225: public function changeSubtractedType(?Type $subtractedType): Type
226: {
227: if ($subtractedType !== null) {
228: $classReflection = $this->getClassReflection();
229: if ($classReflection->getAllowedSubTypes() !== null) {
230: $objectType = $this->getStaticObjectType()->changeSubtractedType($subtractedType);
231: if ($objectType instanceof NeverType) {
232: return $objectType;
233: }
234:
235: if ($objectType instanceof ObjectType && $objectType->getSubtractedType() !== null) {
236: return new self($classReflection, $this->types, $objectType->getSubtractedType(), $this->variances);
237: }
238:
239: return TypeCombinator::intersect($this, $objectType);
240: }
241: }
242:
243: return new self(
244: $this->classReflection,
245: $this->types,
246: $subtractedType,
247: $this->variances,
248: );
249: }
250:
251: public function inferTemplateTypes(Type $receivedType): TemplateTypeMap
252: {
253: return $this->getStaticObjectType()->inferTemplateTypes($receivedType);
254: }
255:
256: public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array
257: {
258: return $this->getStaticObjectType()->getReferencedTemplateTypes($positionVariance);
259: }
260:
261: public function toPhpDocNode(): TypeNode
262: {
263: /** @var IdentifierTypeNode $parent */
264: $parent = parent::toPhpDocNode();
265: return new GenericTypeNode(
266: $parent,
267: array_map(static fn (Type $type) => $type->toPhpDocNode(), $this->types),
268: array_map(static fn (TemplateTypeVariance $variance) => $variance->toPhpDocNodeVariance(), $this->variances),
269: );
270: }
271:
272: }
273: