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