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