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