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