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