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\ClassMemberAccessAnswerer;
9: use PHPStan\Reflection\ClassReflection;
10: use PHPStan\Reflection\ExtendedMethodReflection;
11: use PHPStan\Reflection\ExtendedPropertyReflection;
12: use PHPStan\Reflection\ReflectionProviderStaticAccessor;
13: use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection;
14: use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection;
15: use PHPStan\ShouldNotHappenException;
16: use PHPStan\Type\AcceptsResult;
17: use PHPStan\Type\CompoundType;
18: use PHPStan\Type\ErrorType;
19: use PHPStan\Type\IntersectionType;
20: use PHPStan\Type\IsSuperTypeOfResult;
21: use PHPStan\Type\ObjectType;
22: use PHPStan\Type\Type;
23: use PHPStan\Type\TypeWithClassName;
24: use PHPStan\Type\UnionType;
25: use PHPStan\Type\VerbosityLevel;
26: use function array_map;
27: use function count;
28: use function implode;
29: use function sprintf;
30:
31: /** @api */
32: class GenericObjectType extends ObjectType
33: {
34:
35: /**
36: * @api
37: * @param array<int, Type> $types
38: * @param array<int, TemplateTypeVariance> $variances
39: */
40: public function __construct(
41: string $mainType,
42: private array $types,
43: ?Type $subtractedType = null,
44: private ?ClassReflection $classReflection = null,
45: private array $variances = [],
46: )
47: {
48: parent::__construct($mainType, $subtractedType, $classReflection);
49: }
50:
51: public function describe(VerbosityLevel $level): string
52: {
53: return sprintf(
54: '%s<%s>',
55: parent::describe($level),
56: implode(', ', array_map(
57: static fn (Type $type, ?TemplateTypeVariance $variance = null): string => TypeProjectionHelper::describe($type, $variance, $level),
58: $this->types,
59: $this->variances,
60: )),
61: );
62: }
63:
64: public function equals(Type $type): bool
65: {
66: if (!$type instanceof self) {
67: return false;
68: }
69:
70: if (!parent::equals($type)) {
71: return false;
72: }
73:
74: if (count($this->types) !== count($type->types)) {
75: return false;
76: }
77:
78: foreach ($this->types as $i => $genericType) {
79: $otherGenericType = $type->types[$i];
80: if (!$genericType->equals($otherGenericType)) {
81: return false;
82: }
83:
84: $variance = $this->variances[$i] ?? TemplateTypeVariance::createInvariant();
85: $otherVariance = $type->variances[$i] ?? TemplateTypeVariance::createInvariant();
86: if (!$variance->equals($otherVariance)) {
87: return false;
88: }
89: }
90:
91: return true;
92: }
93:
94: public function getReferencedClasses(): array
95: {
96: $classes = parent::getReferencedClasses();
97: foreach ($this->types as $type) {
98: foreach ($type->getReferencedClasses() as $referencedClass) {
99: $classes[] = $referencedClass;
100: }
101: }
102:
103: return $classes;
104: }
105:
106: /** @return array<int, Type> */
107: public function getTypes(): array
108: {
109: return $this->types;
110: }
111:
112: /** @return array<int, TemplateTypeVariance> */
113: public function getVariances(): array
114: {
115: return $this->variances;
116: }
117:
118: public function accepts(Type $type, bool $strictTypes): AcceptsResult
119: {
120: if ($type instanceof CompoundType) {
121: return $type->isAcceptedBy($this, $strictTypes);
122: }
123:
124: return $this->isSuperTypeOfInternal($type, true)->toAcceptsResult();
125: }
126:
127: public function isSuperTypeOf(Type $type): IsSuperTypeOfResult
128: {
129: if ($type instanceof CompoundType) {
130: return $type->isSubTypeOf($this);
131: }
132:
133: return $this->isSuperTypeOfInternal($type, false);
134: }
135:
136: private function isSuperTypeOfInternal(Type $type, bool $acceptsContext): IsSuperTypeOfResult
137: {
138: $nakedSuperTypeOf = parent::isSuperTypeOf($type);
139: if ($nakedSuperTypeOf->no()) {
140: return $nakedSuperTypeOf;
141: }
142:
143: if (!$type instanceof ObjectType) {
144: return $nakedSuperTypeOf;
145: }
146:
147: $ancestor = $type->getAncestorWithClassName($this->getClassName());
148: if ($ancestor === null) {
149: return $nakedSuperTypeOf;
150: }
151: if (!$ancestor instanceof self) {
152: if ($acceptsContext) {
153: return $nakedSuperTypeOf;
154: }
155:
156: return $nakedSuperTypeOf->and(IsSuperTypeOfResult::createMaybe());
157: }
158:
159: if (count($this->types) !== count($ancestor->types)) {
160: return IsSuperTypeOfResult::createNo();
161: }
162:
163: $classReflection = $this->getClassReflection();
164: if ($classReflection === null) {
165: return $nakedSuperTypeOf;
166: }
167:
168: $typeList = $classReflection->typeMapToList($classReflection->getTemplateTypeMap());
169: $results = [];
170: foreach ($typeList as $i => $templateType) {
171: if (!isset($ancestor->types[$i])) {
172: continue;
173: }
174: if (!isset($this->types[$i])) {
175: continue;
176: }
177: if ($templateType instanceof ErrorType) {
178: continue;
179: }
180: if (!$templateType instanceof TemplateType) {
181: throw new ShouldNotHappenException();
182: }
183:
184: $thisVariance = $this->variances[$i] ?? TemplateTypeVariance::createInvariant();
185: $ancestorVariance = $ancestor->variances[$i] ?? TemplateTypeVariance::createInvariant();
186: if (!$thisVariance->invariant()) {
187: $results[] = $thisVariance->isValidVariance($templateType, $this->types[$i], $ancestor->types[$i]);
188: } else {
189: $results[] = $templateType->isValidVariance($this->types[$i], $ancestor->types[$i]);
190: }
191:
192: $results[] = IsSuperTypeOfResult::createFromBoolean($thisVariance->validPosition($ancestorVariance));
193: }
194:
195: if (count($results) === 0) {
196: return $nakedSuperTypeOf;
197: }
198:
199: $result = IsSuperTypeOfResult::createYes();
200: foreach ($results as $innerResult) {
201: $result = $result->and($innerResult);
202: }
203:
204: return $result;
205: }
206:
207: public function getClassReflection(): ?ClassReflection
208: {
209: if ($this->classReflection !== null) {
210: return $this->classReflection;
211: }
212:
213: $reflectionProvider = ReflectionProviderStaticAccessor::getInstance();
214: if (!$reflectionProvider->hasClass($this->getClassName())) {
215: return null;
216: }
217:
218: return $this->classReflection = $reflectionProvider->getClass($this->getClassName())
219: ->withTypes($this->types)
220: ->withVariances($this->variances);
221: }
222:
223: public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
224: {
225: return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty();
226: }
227:
228: public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
229: {
230: $prototype = parent::getUnresolvedPropertyPrototype($propertyName, $scope);
231:
232: return $prototype->doNotResolveTemplateTypeMapToBounds();
233: }
234:
235: public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): ExtendedMethodReflection
236: {
237: return $this->getUnresolvedMethodPrototype($methodName, $scope)->getTransformedMethod();
238: }
239:
240: public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection
241: {
242: $prototype = parent::getUnresolvedMethodPrototype($methodName, $scope);
243:
244: return $prototype->doNotResolveTemplateTypeMapToBounds();
245: }
246:
247: public function inferTemplateTypes(Type $receivedType): TemplateTypeMap
248: {
249: if ($receivedType instanceof UnionType || $receivedType instanceof IntersectionType) {
250: return $receivedType->inferTemplateTypesOn($this);
251: }
252:
253: if (!$receivedType instanceof TypeWithClassName) {
254: return TemplateTypeMap::createEmpty();
255: }
256:
257: $ancestor = $receivedType->getAncestorWithClassName($this->getClassName());
258:
259: if ($ancestor === null) {
260: return TemplateTypeMap::createEmpty();
261: }
262: $ancestorClassReflection = $ancestor->getClassReflection();
263: if ($ancestorClassReflection === null) {
264: return TemplateTypeMap::createEmpty();
265: }
266:
267: $otherTypes = $ancestorClassReflection->typeMapToList($ancestorClassReflection->getActiveTemplateTypeMap());
268: $typeMap = TemplateTypeMap::createEmpty();
269:
270: foreach ($this->getTypes() as $i => $type) {
271: $other = $otherTypes[$i] ?? new ErrorType();
272: $typeMap = $typeMap->union($type->inferTemplateTypes($other));
273: }
274:
275: return $typeMap;
276: }
277:
278: public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array
279: {
280: $classReflection = $this->getClassReflection();
281: if ($classReflection !== null) {
282: $typeList = $classReflection->typeMapToList($classReflection->getTemplateTypeMap());
283: } else {
284: $typeList = [];
285: }
286:
287: $references = [];
288:
289: foreach ($this->types as $i => $type) {
290: $effectiveVariance = $this->variances[$i] ?? TemplateTypeVariance::createInvariant();
291: if ($effectiveVariance->invariant() && isset($typeList[$i]) && $typeList[$i] instanceof TemplateType) {
292: $effectiveVariance = $typeList[$i]->getVariance();
293: }
294:
295: $variance = $positionVariance->compose($effectiveVariance);
296: foreach ($type->getReferencedTemplateTypes($variance) as $reference) {
297: $references[] = $reference;
298: }
299: }
300:
301: return $references;
302: }
303:
304: public function traverse(callable $cb): Type
305: {
306: $subtractedType = $this->getSubtractedType() !== null ? $cb($this->getSubtractedType()) : null;
307:
308: $typesChanged = false;
309: $types = [];
310: foreach ($this->types as $type) {
311: $newType = $cb($type);
312: $types[] = $newType;
313: if ($newType === $type) {
314: continue;
315: }
316:
317: $typesChanged = true;
318: }
319:
320: if ($subtractedType !== $this->getSubtractedType() || $typesChanged) {
321: return $this->recreate($this->getClassName(), $types, $subtractedType, $this->variances);
322: }
323:
324: return $this;
325: }
326:
327: public function traverseSimultaneously(Type $right, callable $cb): Type
328: {
329: if (!$right instanceof TypeWithClassName) {
330: return $this;
331: }
332:
333: $ancestor = $right->getAncestorWithClassName($this->getClassName());
334: if (!$ancestor instanceof self) {
335: return $this;
336: }
337:
338: if (count($this->types) !== count($ancestor->types)) {
339: return $this;
340: }
341:
342: $typesChanged = false;
343: $types = [];
344: foreach ($this->types as $i => $leftType) {
345: $rightType = $ancestor->types[$i];
346: $newType = $cb($leftType, $rightType);
347: $types[] = $newType;
348: if ($newType === $leftType) {
349: continue;
350: }
351:
352: $typesChanged = true;
353: }
354:
355: if ($typesChanged) {
356: return $this->recreate($this->getClassName(), $types, null);
357: }
358:
359: return $this;
360: }
361:
362: /**
363: * @param Type[] $types
364: * @param TemplateTypeVariance[] $variances
365: */
366: protected function recreate(string $className, array $types, ?Type $subtractedType, array $variances = []): self
367: {
368: return new self(
369: $className,
370: $types,
371: $subtractedType,
372: null,
373: $variances,
374: );
375: }
376:
377: public function changeSubtractedType(?Type $subtractedType): Type
378: {
379: return new self($this->getClassName(), $this->types, $subtractedType, null, $this->variances);
380: }
381:
382: public function toPhpDocNode(): TypeNode
383: {
384: /** @var IdentifierTypeNode $parent */
385: $parent = parent::toPhpDocNode();
386: return new GenericTypeNode(
387: $parent,
388: array_map(static fn (Type $type) => $type->toPhpDocNode(), $this->types),
389: array_map(static fn (TemplateTypeVariance $variance) => $variance->toPhpDocNodeVariance(), $this->variances),
390: );
391: }
392:
393: }
394: