| 1: | <?php declare(strict_types = 1); | 
| 2: |  | 
| 3: | namespace PHPStan\Type\Generic; | 
| 4: |  | 
| 5: | use PHPStan\Reflection\ClassMemberAccessAnswerer; | 
| 6: | use PHPStan\Reflection\ClassReflection; | 
| 7: | use PHPStan\Reflection\MethodReflection; | 
| 8: | use PHPStan\Reflection\PropertyReflection; | 
| 9: | use PHPStan\Reflection\ReflectionProviderStaticAccessor; | 
| 10: | use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection; | 
| 11: | use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection; | 
| 12: | use PHPStan\ShouldNotHappenException; | 
| 13: | use PHPStan\TrinaryLogic; | 
| 14: | use PHPStan\Type\CompoundType; | 
| 15: | use PHPStan\Type\ErrorType; | 
| 16: | use PHPStan\Type\IntersectionType; | 
| 17: | use PHPStan\Type\ObjectType; | 
| 18: | use PHPStan\Type\Type; | 
| 19: | use PHPStan\Type\TypeWithClassName; | 
| 20: | use PHPStan\Type\UnionType; | 
| 21: | use PHPStan\Type\VerbosityLevel; | 
| 22: | use function array_map; | 
| 23: | use function count; | 
| 24: | use function implode; | 
| 25: | use function sprintf; | 
| 26: |  | 
| 27: |  | 
| 28: | class GenericObjectType extends ObjectType | 
| 29: | { | 
| 30: |  | 
| 31: |  | 
| 32: |  | 
| 33: |  | 
| 34: |  | 
| 35: | public function __construct( | 
| 36: | string $mainType, | 
| 37: | private array $types, | 
| 38: | ?Type $subtractedType = null, | 
| 39: | private ?ClassReflection $classReflection = null, | 
| 40: | ) | 
| 41: | { | 
| 42: | parent::__construct($mainType, $subtractedType, $classReflection); | 
| 43: | } | 
| 44: |  | 
| 45: | public function describe(VerbosityLevel $level): string | 
| 46: | { | 
| 47: | return sprintf( | 
| 48: | '%s<%s>', | 
| 49: | parent::describe($level), | 
| 50: | implode(', ', array_map(static fn (Type $type): string => $type->describe($level), $this->types)), | 
| 51: | ); | 
| 52: | } | 
| 53: |  | 
| 54: | public function equals(Type $type): bool | 
| 55: | { | 
| 56: | if (!$type instanceof self) { | 
| 57: | return false; | 
| 58: | } | 
| 59: |  | 
| 60: | if (!parent::equals($type)) { | 
| 61: | return false; | 
| 62: | } | 
| 63: |  | 
| 64: | if (count($this->types) !== count($type->types)) { | 
| 65: | return false; | 
| 66: | } | 
| 67: |  | 
| 68: | foreach ($this->types as $i => $genericType) { | 
| 69: | $otherGenericType = $type->types[$i]; | 
| 70: | if (!$genericType->equals($otherGenericType)) { | 
| 71: | return false; | 
| 72: | } | 
| 73: | } | 
| 74: |  | 
| 75: | return true; | 
| 76: | } | 
| 77: |  | 
| 78: |  | 
| 79: |  | 
| 80: |  | 
| 81: | public function getReferencedClasses(): array | 
| 82: | { | 
| 83: | $classes = parent::getReferencedClasses(); | 
| 84: | foreach ($this->types as $type) { | 
| 85: | foreach ($type->getReferencedClasses() as $referencedClass) { | 
| 86: | $classes[] = $referencedClass; | 
| 87: | } | 
| 88: | } | 
| 89: |  | 
| 90: | return $classes; | 
| 91: | } | 
| 92: |  | 
| 93: |  | 
| 94: | public function getTypes(): array | 
| 95: | { | 
| 96: | return $this->types; | 
| 97: | } | 
| 98: |  | 
| 99: | public function accepts(Type $type, bool $strictTypes): TrinaryLogic | 
| 100: | { | 
| 101: | if ($type instanceof CompoundType) { | 
| 102: | return $type->isAcceptedBy($this, $strictTypes); | 
| 103: | } | 
| 104: |  | 
| 105: | return $this->isSuperTypeOfInternal($type, true); | 
| 106: | } | 
| 107: |  | 
| 108: | public function isSuperTypeOf(Type $type): TrinaryLogic | 
| 109: | { | 
| 110: | if ($type instanceof CompoundType) { | 
| 111: | return $type->isSubTypeOf($this); | 
| 112: | } | 
| 113: |  | 
| 114: | return $this->isSuperTypeOfInternal($type, false); | 
| 115: | } | 
| 116: |  | 
| 117: | private function isSuperTypeOfInternal(Type $type, bool $acceptsContext): TrinaryLogic | 
| 118: | { | 
| 119: | $nakedSuperTypeOf = parent::isSuperTypeOf($type); | 
| 120: | if ($nakedSuperTypeOf->no()) { | 
| 121: | return $nakedSuperTypeOf; | 
| 122: | } | 
| 123: |  | 
| 124: | if (!$type instanceof ObjectType) { | 
| 125: | return $nakedSuperTypeOf; | 
| 126: | } | 
| 127: |  | 
| 128: | $ancestor = $type->getAncestorWithClassName($this->getClassName()); | 
| 129: | if ($ancestor === null) { | 
| 130: | return $nakedSuperTypeOf; | 
| 131: | } | 
| 132: | if (!$ancestor instanceof self) { | 
| 133: | if ($acceptsContext) { | 
| 134: | return $nakedSuperTypeOf; | 
| 135: | } | 
| 136: |  | 
| 137: | return $nakedSuperTypeOf->and(TrinaryLogic::createMaybe()); | 
| 138: | } | 
| 139: |  | 
| 140: | if (count($this->types) !== count($ancestor->types)) { | 
| 141: | return TrinaryLogic::createNo(); | 
| 142: | } | 
| 143: |  | 
| 144: | $classReflection = $this->getClassReflection(); | 
| 145: | if ($classReflection === null) { | 
| 146: | return $nakedSuperTypeOf; | 
| 147: | } | 
| 148: |  | 
| 149: | $typeList = $classReflection->typeMapToList($classReflection->getTemplateTypeMap()); | 
| 150: | $results = []; | 
| 151: | foreach ($typeList as $i => $templateType) { | 
| 152: | if (!isset($ancestor->types[$i])) { | 
| 153: | continue; | 
| 154: | } | 
| 155: | if (!isset($this->types[$i])) { | 
| 156: | continue; | 
| 157: | } | 
| 158: | if ($templateType instanceof ErrorType) { | 
| 159: | continue; | 
| 160: | } | 
| 161: | if (!$templateType instanceof TemplateType) { | 
| 162: | throw new ShouldNotHappenException(); | 
| 163: | } | 
| 164: |  | 
| 165: | $results[] = $templateType->isValidVariance($this->types[$i], $ancestor->types[$i]); | 
| 166: | } | 
| 167: |  | 
| 168: | if (count($results) === 0) { | 
| 169: | return $nakedSuperTypeOf; | 
| 170: | } | 
| 171: |  | 
| 172: | return $nakedSuperTypeOf->and(...$results); | 
| 173: | } | 
| 174: |  | 
| 175: | public function getClassReflection(): ?ClassReflection | 
| 176: | { | 
| 177: | if ($this->classReflection !== null) { | 
| 178: | return $this->classReflection; | 
| 179: | } | 
| 180: |  | 
| 181: | $reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); | 
| 182: | if (!$reflectionProvider->hasClass($this->getClassName())) { | 
| 183: | return null; | 
| 184: | } | 
| 185: |  | 
| 186: | return $this->classReflection = $reflectionProvider->getClass($this->getClassName())->withTypes($this->types); | 
| 187: | } | 
| 188: |  | 
| 189: | public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection | 
| 190: | { | 
| 191: | return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty(); | 
| 192: | } | 
| 193: |  | 
| 194: | public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection | 
| 195: | { | 
| 196: | $prototype = parent::getUnresolvedPropertyPrototype($propertyName, $scope); | 
| 197: |  | 
| 198: | return $prototype->doNotResolveTemplateTypeMapToBounds(); | 
| 199: | } | 
| 200: |  | 
| 201: | public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): MethodReflection | 
| 202: | { | 
| 203: | return $this->getUnresolvedMethodPrototype($methodName, $scope)->getTransformedMethod(); | 
| 204: | } | 
| 205: |  | 
| 206: | public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection | 
| 207: | { | 
| 208: | $prototype = parent::getUnresolvedMethodPrototype($methodName, $scope); | 
| 209: |  | 
| 210: | return $prototype->doNotResolveTemplateTypeMapToBounds(); | 
| 211: | } | 
| 212: |  | 
| 213: | public function inferTemplateTypes(Type $receivedType): TemplateTypeMap | 
| 214: | { | 
| 215: | if ($receivedType instanceof UnionType || $receivedType instanceof IntersectionType) { | 
| 216: | return $receivedType->inferTemplateTypesOn($this); | 
| 217: | } | 
| 218: |  | 
| 219: | if (!$receivedType instanceof TypeWithClassName) { | 
| 220: | return TemplateTypeMap::createEmpty(); | 
| 221: | } | 
| 222: |  | 
| 223: | $ancestor = $receivedType->getAncestorWithClassName($this->getClassName()); | 
| 224: |  | 
| 225: | if ($ancestor === null) { | 
| 226: | return TemplateTypeMap::createEmpty(); | 
| 227: | } | 
| 228: | $ancestorClassReflection = $ancestor->getClassReflection(); | 
| 229: | if ($ancestorClassReflection === null) { | 
| 230: | return TemplateTypeMap::createEmpty(); | 
| 231: | } | 
| 232: |  | 
| 233: | $otherTypes = $ancestorClassReflection->typeMapToList($ancestorClassReflection->getActiveTemplateTypeMap()); | 
| 234: | $typeMap = TemplateTypeMap::createEmpty(); | 
| 235: |  | 
| 236: | foreach ($this->getTypes() as $i => $type) { | 
| 237: | $other = $otherTypes[$i] ?? new ErrorType(); | 
| 238: | $typeMap = $typeMap->union($type->inferTemplateTypes($other)); | 
| 239: | } | 
| 240: |  | 
| 241: | return $typeMap; | 
| 242: | } | 
| 243: |  | 
| 244: | public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array | 
| 245: | { | 
| 246: | $classReflection = $this->getClassReflection(); | 
| 247: | if ($classReflection !== null) { | 
| 248: | $typeList = $classReflection->typeMapToList($classReflection->getTemplateTypeMap()); | 
| 249: | } else { | 
| 250: | $typeList = []; | 
| 251: | } | 
| 252: |  | 
| 253: | $references = []; | 
| 254: |  | 
| 255: | foreach ($this->types as $i => $type) { | 
| 256: | $variance = $positionVariance->compose( | 
| 257: | isset($typeList[$i]) && $typeList[$i] instanceof TemplateType | 
| 258: | ? $typeList[$i]->getVariance() | 
| 259: | : TemplateTypeVariance::createInvariant(), | 
| 260: | ); | 
| 261: | foreach ($type->getReferencedTemplateTypes($variance) as $reference) { | 
| 262: | $references[] = $reference; | 
| 263: | } | 
| 264: | } | 
| 265: |  | 
| 266: | return $references; | 
| 267: | } | 
| 268: |  | 
| 269: | public function traverse(callable $cb): Type | 
| 270: | { | 
| 271: | $subtractedType = $this->getSubtractedType() !== null ? $cb($this->getSubtractedType()) : null; | 
| 272: |  | 
| 273: | $typesChanged = false; | 
| 274: | $types = []; | 
| 275: | foreach ($this->types as $type) { | 
| 276: | $newType = $cb($type); | 
| 277: | $types[] = $newType; | 
| 278: | if ($newType === $type) { | 
| 279: | continue; | 
| 280: | } | 
| 281: |  | 
| 282: | $typesChanged = true; | 
| 283: | } | 
| 284: |  | 
| 285: | if ($subtractedType !== $this->getSubtractedType() || $typesChanged) { | 
| 286: | return $this->recreate($this->getClassName(), $types, $subtractedType); | 
| 287: | } | 
| 288: |  | 
| 289: | return $this; | 
| 290: | } | 
| 291: |  | 
| 292: |  | 
| 293: |  | 
| 294: |  | 
| 295: | protected function recreate(string $className, array $types, ?Type $subtractedType): self | 
| 296: | { | 
| 297: | return new self( | 
| 298: | $className, | 
| 299: | $types, | 
| 300: | $subtractedType, | 
| 301: | ); | 
| 302: | } | 
| 303: |  | 
| 304: | public function changeSubtractedType(?Type $subtractedType): Type | 
| 305: | { | 
| 306: | return new self($this->getClassName(), $this->types, $subtractedType); | 
| 307: | } | 
| 308: |  | 
| 309: |  | 
| 310: |  | 
| 311: |  | 
| 312: | public static function __set_state(array $properties): Type | 
| 313: | { | 
| 314: | return new self( | 
| 315: | $properties['className'], | 
| 316: | $properties['types'], | 
| 317: | $properties['subtractedType'] ?? null, | 
| 318: | ); | 
| 319: | } | 
| 320: |  | 
| 321: | } | 
| 322: |  |