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 getInstanceProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
236: {
237: return $this->getUnresolvedInstancePropertyPrototype($propertyName, $scope)->getTransformedProperty();
238: }
239:
240: public function getUnresolvedInstancePropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
241: {
242: $prototype = parent::getUnresolvedInstancePropertyPrototype($propertyName, $scope);
243:
244: return $prototype->doNotResolveTemplateTypeMapToBounds();
245: }
246:
247: public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
248: {
249: return $this->getUnresolvedStaticPropertyPrototype($propertyName, $scope)->getTransformedProperty();
250: }
251:
252: public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
253: {
254: $prototype = parent::getUnresolvedStaticPropertyPrototype($propertyName, $scope);
255:
256: return $prototype->doNotResolveTemplateTypeMapToBounds();
257: }
258:
259: public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): ExtendedMethodReflection
260: {
261: return $this->getUnresolvedMethodPrototype($methodName, $scope)->getTransformedMethod();
262: }
263:
264: public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection
265: {
266: $prototype = parent::getUnresolvedMethodPrototype($methodName, $scope);
267:
268: return $prototype->doNotResolveTemplateTypeMapToBounds();
269: }
270:
271: public function inferTemplateTypes(Type $receivedType): TemplateTypeMap
272: {
273: if ($receivedType instanceof UnionType || $receivedType instanceof IntersectionType) {
274: return $receivedType->inferTemplateTypesOn($this);
275: }
276:
277: if (!$receivedType instanceof TypeWithClassName) {
278: return TemplateTypeMap::createEmpty();
279: }
280:
281: $ancestor = $receivedType->getAncestorWithClassName($this->getClassName());
282:
283: if ($ancestor === null) {
284: return TemplateTypeMap::createEmpty();
285: }
286: $ancestorClassReflection = $ancestor->getClassReflection();
287: if ($ancestorClassReflection === null) {
288: return TemplateTypeMap::createEmpty();
289: }
290:
291: $otherTypes = $ancestorClassReflection->typeMapToList($ancestorClassReflection->getActiveTemplateTypeMap());
292: $typeMap = TemplateTypeMap::createEmpty();
293:
294: foreach ($this->getTypes() as $i => $type) {
295: $other = $otherTypes[$i] ?? new ErrorType();
296: $typeMap = $typeMap->union($type->inferTemplateTypes($other));
297: }
298:
299: return $typeMap;
300: }
301:
302: public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array
303: {
304: $classReflection = $this->getClassReflection();
305: if ($classReflection !== null) {
306: $typeList = $classReflection->typeMapToList($classReflection->getTemplateTypeMap());
307: } else {
308: $typeList = [];
309: }
310:
311: $references = [];
312:
313: foreach ($this->types as $i => $type) {
314: $effectiveVariance = $this->variances[$i] ?? TemplateTypeVariance::createInvariant();
315: if ($effectiveVariance->invariant() && isset($typeList[$i]) && $typeList[$i] instanceof TemplateType) {
316: $effectiveVariance = $typeList[$i]->getVariance();
317: }
318:
319: $variance = $positionVariance->compose($effectiveVariance);
320: foreach ($type->getReferencedTemplateTypes($variance) as $reference) {
321: $references[] = $reference;
322: }
323: }
324:
325: return $references;
326: }
327:
328: public function traverse(callable $cb): Type
329: {
330: $subtractedType = $this->getSubtractedType() !== null ? $cb($this->getSubtractedType()) : null;
331:
332: $typesChanged = false;
333: $types = [];
334: foreach ($this->types as $type) {
335: $newType = $cb($type);
336: $types[] = $newType;
337: if ($newType === $type) {
338: continue;
339: }
340:
341: $typesChanged = true;
342: }
343:
344: if ($subtractedType !== $this->getSubtractedType() || $typesChanged) {
345: return $this->recreate($this->getClassName(), $types, $subtractedType, $this->variances);
346: }
347:
348: return $this;
349: }
350:
351: public function traverseSimultaneously(Type $right, callable $cb): Type
352: {
353: if (!$right instanceof TypeWithClassName) {
354: return $this;
355: }
356:
357: $ancestor = $right->getAncestorWithClassName($this->getClassName());
358: if (!$ancestor instanceof self) {
359: return $this;
360: }
361:
362: if (count($this->types) !== count($ancestor->types)) {
363: return $this;
364: }
365:
366: $typesChanged = false;
367: $types = [];
368: foreach ($this->types as $i => $leftType) {
369: $rightType = $ancestor->types[$i];
370: $newType = $cb($leftType, $rightType);
371: $types[] = $newType;
372: if ($newType === $leftType) {
373: continue;
374: }
375:
376: $typesChanged = true;
377: }
378:
379: if ($typesChanged) {
380: return $this->recreate($this->getClassName(), $types, null);
381: }
382:
383: return $this;
384: }
385:
386: /**
387: * @param Type[] $types
388: * @param TemplateTypeVariance[] $variances
389: */
390: protected function recreate(string $className, array $types, ?Type $subtractedType, array $variances = []): self
391: {
392: return new self(
393: $className,
394: $types,
395: $subtractedType,
396: null,
397: $variances,
398: );
399: }
400:
401: public function changeSubtractedType(?Type $subtractedType): Type
402: {
403: return new self($this->getClassName(), $this->types, $subtractedType, null, $this->variances);
404: }
405:
406: public function toPhpDocNode(): TypeNode
407: {
408: /** @var IdentifierTypeNode $parent */
409: $parent = parent::toPhpDocNode();
410: return new GenericTypeNode(
411: $parent,
412: array_map(static fn (Type $type) => $type->toPhpDocNode(), $this->types),
413: array_map(static fn (TemplateTypeVariance $variance) => $variance->toPhpDocNodeVariance(), $this->variances),
414: );
415: }
416:
417: }
418: