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