|   1:  | <?php declare(strict_types = 1); | 
|   2:  |  | 
|   3:  | namespace PHPStan\Type; | 
|   4:  |  | 
|   5:  | use DateTime; | 
|   6:  | use DateTimeImmutable; | 
|   7:  | use DateTimeInterface; | 
|   8:  | use PHPStan\Reflection\ClassMemberAccessAnswerer; | 
|   9:  | use PHPStan\Reflection\ConstantReflection; | 
|  10:  | use PHPStan\Reflection\ExtendedMethodReflection; | 
|  11:  | use PHPStan\Reflection\ParametersAcceptor; | 
|  12:  | use PHPStan\Reflection\PropertyReflection; | 
|  13:  | use PHPStan\Reflection\Type\UnionTypeUnresolvedMethodPrototypeReflection; | 
|  14:  | use PHPStan\Reflection\Type\UnionTypeUnresolvedPropertyPrototypeReflection; | 
|  15:  | use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection; | 
|  16:  | use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection; | 
|  17:  | use PHPStan\ShouldNotHappenException; | 
|  18:  | use PHPStan\TrinaryLogic; | 
|  19:  | use PHPStan\Type\Constant\ConstantBooleanType; | 
|  20:  | use PHPStan\Type\Generic\GenericClassStringType; | 
|  21:  | use PHPStan\Type\Generic\TemplateMixedType; | 
|  22:  | use PHPStan\Type\Generic\TemplateType; | 
|  23:  | use PHPStan\Type\Generic\TemplateTypeMap; | 
|  24:  | use PHPStan\Type\Generic\TemplateTypeVariance; | 
|  25:  | use PHPStan\Type\Generic\TemplateUnionType; | 
|  26:  | use PHPStan\Type\Traits\NonGeneralizableTypeTrait; | 
|  27:  | use function array_map; | 
|  28:  | use function count; | 
|  29:  | use function implode; | 
|  30:  | use function sprintf; | 
|  31:  | use function strpos; | 
|  32:  |  | 
|  33:  |  | 
|  34:  | class UnionType implements CompoundType | 
|  35:  | { | 
|  36:  |  | 
|  37:  | 	use NonGeneralizableTypeTrait; | 
|  38:  |  | 
|  39:  | 	private bool $sortedTypes = false; | 
|  40:  |  | 
|  41:  | 	 | 
|  42:  | 	private array $cachedDescriptions = []; | 
|  43:  |  | 
|  44:  | 	 | 
|  45:  |  | 
|  46:  |  | 
|  47:  |  | 
|  48:  | 	public function __construct(private array $types, private bool $normalized = false) | 
|  49:  | 	{ | 
|  50:  | 		$throwException = static function () use ($types): void { | 
|  51:  | 			throw new ShouldNotHappenException(sprintf( | 
|  52:  | 				'Cannot create %s with: %s', | 
|  53:  | 				self::class, | 
|  54:  | 				implode(', ', array_map(static fn (Type $type): string => $type->describe(VerbosityLevel::value()), $types)), | 
|  55:  | 			)); | 
|  56:  | 		}; | 
|  57:  | 		if (count($types) < 2) { | 
|  58:  | 			$throwException(); | 
|  59:  | 		} | 
|  60:  | 		foreach ($types as $type) { | 
|  61:  | 			if (!($type instanceof UnionType)) { | 
|  62:  | 				continue; | 
|  63:  | 			} | 
|  64:  | 			if ($type instanceof TemplateType) { | 
|  65:  | 				continue; | 
|  66:  | 			} | 
|  67:  |  | 
|  68:  | 			$throwException(); | 
|  69:  | 		} | 
|  70:  | 	} | 
|  71:  |  | 
|  72:  | 	 | 
|  73:  |  | 
|  74:  |  | 
|  75:  | 	public function getTypes(): array | 
|  76:  | 	{ | 
|  77:  | 		return $this->types; | 
|  78:  | 	} | 
|  79:  |  | 
|  80:  | 	public function isNormalized(): bool | 
|  81:  | 	{ | 
|  82:  | 		return $this->normalized; | 
|  83:  | 	} | 
|  84:  |  | 
|  85:  | 	 | 
|  86:  |  | 
|  87:  |  | 
|  88:  | 	private function getSortedTypes(): array | 
|  89:  | 	{ | 
|  90:  | 		if ($this->sortedTypes) { | 
|  91:  | 			return $this->types; | 
|  92:  | 		} | 
|  93:  |  | 
|  94:  | 		$this->types = UnionTypeHelper::sortTypes($this->types); | 
|  95:  | 		$this->sortedTypes = true; | 
|  96:  |  | 
|  97:  | 		return $this->types; | 
|  98:  | 	} | 
|  99:  |  | 
| 100:  | 	 | 
| 101:  |  | 
| 102:  |  | 
| 103:  | 	public function getReferencedClasses(): array | 
| 104:  | 	{ | 
| 105:  | 		$classes = []; | 
| 106:  | 		foreach ($this->types as $type) { | 
| 107:  | 			foreach ($type->getReferencedClasses() as $className) { | 
| 108:  | 				$classes[] = $className; | 
| 109:  | 			} | 
| 110:  | 		} | 
| 111:  |  | 
| 112:  | 		return $classes; | 
| 113:  | 	} | 
| 114:  |  | 
| 115:  | 	public function getArrays(): array | 
| 116:  | 	{ | 
| 117:  | 		return $this->pickTypes(static fn (Type $type) => $type->getArrays()); | 
| 118:  | 	} | 
| 119:  |  | 
| 120:  | 	public function getConstantArrays(): array | 
| 121:  | 	{ | 
| 122:  | 		return $this->pickTypes(static fn (Type $type) => $type->getConstantArrays()); | 
| 123:  | 	} | 
| 124:  |  | 
| 125:  | 	public function getConstantStrings(): array | 
| 126:  | 	{ | 
| 127:  | 		return $this->pickTypes(static fn (Type $type) => $type->getConstantStrings()); | 
| 128:  | 	} | 
| 129:  |  | 
| 130:  | 	public function accepts(Type $type, bool $strictTypes): TrinaryLogic | 
| 131:  | 	{ | 
| 132:  | 		if ( | 
| 133:  | 			$type->equals(new ObjectType(DateTimeInterface::class)) | 
| 134:  | 			&& $this->accepts( | 
| 135:  | 				new UnionType([new ObjectType(DateTime::class), new ObjectType(DateTimeImmutable::class)]), | 
| 136:  | 				$strictTypes, | 
| 137:  | 			)->yes() | 
| 138:  | 		) { | 
| 139:  | 			return TrinaryLogic::createYes(); | 
| 140:  | 		} | 
| 141:  |  | 
| 142:  | 		if ($type instanceof CompoundType && !$type instanceof CallableType && !$type instanceof TemplateType && !$type instanceof IntersectionType) { | 
| 143:  | 			return $type->isAcceptedBy($this, $strictTypes); | 
| 144:  | 		} | 
| 145:  |  | 
| 146:  | 		$result = TrinaryLogic::createNo()->lazyOr($this->getTypes(), static fn (Type $innerType) => $innerType->accepts($type, $strictTypes)); | 
| 147:  | 		if ($result->yes()) { | 
| 148:  | 			return $result; | 
| 149:  | 		} | 
| 150:  |  | 
| 151:  | 		if ($type instanceof TemplateUnionType) { | 
| 152:  | 			return $result->or($type->isAcceptedBy($this, $strictTypes)); | 
| 153:  | 		} | 
| 154:  |  | 
| 155:  | 		return $result; | 
| 156:  | 	} | 
| 157:  |  | 
| 158:  | 	public function isSuperTypeOf(Type $otherType): TrinaryLogic | 
| 159:  | 	{ | 
| 160:  | 		if ( | 
| 161:  | 			($otherType instanceof self && !$otherType instanceof TemplateUnionType) | 
| 162:  | 			|| $otherType instanceof IterableType | 
| 163:  | 			|| $otherType instanceof NeverType | 
| 164:  | 			|| $otherType instanceof ConditionalType | 
| 165:  | 			|| $otherType instanceof ConditionalTypeForParameter | 
| 166:  | 			|| $otherType instanceof IntegerRangeType | 
| 167:  | 		) { | 
| 168:  | 			return $otherType->isSubTypeOf($this); | 
| 169:  | 		} | 
| 170:  |  | 
| 171:  | 		$result = TrinaryLogic::createNo()->lazyOr($this->getTypes(), static fn (Type $innerType) => $innerType->isSuperTypeOf($otherType)); | 
| 172:  | 		if ($result->yes()) { | 
| 173:  | 			return $result; | 
| 174:  | 		} | 
| 175:  |  | 
| 176:  | 		if ($otherType instanceof TemplateUnionType) { | 
| 177:  | 			return $result->or($otherType->isSubTypeOf($this)); | 
| 178:  | 		} | 
| 179:  |  | 
| 180:  | 		return $result; | 
| 181:  | 	} | 
| 182:  |  | 
| 183:  | 	public function isSubTypeOf(Type $otherType): TrinaryLogic | 
| 184:  | 	{ | 
| 185:  | 		return TrinaryLogic::lazyExtremeIdentity($this->getTypes(), static fn (Type $innerType) => $otherType->isSuperTypeOf($innerType)); | 
| 186:  | 	} | 
| 187:  |  | 
| 188:  | 	public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic | 
| 189:  | 	{ | 
| 190:  | 		return TrinaryLogic::lazyExtremeIdentity($this->getTypes(), static fn (Type $innerType) => $acceptingType->accepts($innerType, $strictTypes)); | 
| 191:  | 	} | 
| 192:  |  | 
| 193:  | 	public function equals(Type $type): bool | 
| 194:  | 	{ | 
| 195:  | 		if (!$type instanceof static) { | 
| 196:  | 			return false; | 
| 197:  | 		} | 
| 198:  |  | 
| 199:  | 		if (count($this->types) !== count($type->types)) { | 
| 200:  | 			return false; | 
| 201:  | 		} | 
| 202:  |  | 
| 203:  | 		$otherTypes = $type->types; | 
| 204:  | 		foreach ($this->types as $innerType) { | 
| 205:  | 			$match = false; | 
| 206:  | 			foreach ($otherTypes as $i => $otherType) { | 
| 207:  | 				if (!$innerType->equals($otherType)) { | 
| 208:  | 					continue; | 
| 209:  | 				} | 
| 210:  |  | 
| 211:  | 				$match = true; | 
| 212:  | 				unset($otherTypes[$i]); | 
| 213:  | 				break; | 
| 214:  | 			} | 
| 215:  |  | 
| 216:  | 			if (!$match) { | 
| 217:  | 				return false; | 
| 218:  | 			} | 
| 219:  | 		} | 
| 220:  |  | 
| 221:  | 		return count($otherTypes) === 0; | 
| 222:  | 	} | 
| 223:  |  | 
| 224:  | 	public function describe(VerbosityLevel $level): string | 
| 225:  | 	{ | 
| 226:  | 		if (isset($this->cachedDescriptions[$level->getLevelValue()])) { | 
| 227:  | 			return $this->cachedDescriptions[$level->getLevelValue()]; | 
| 228:  | 		} | 
| 229:  | 		$joinTypes = static function (array $types) use ($level): string { | 
| 230:  | 			$typeNames = []; | 
| 231:  | 			foreach ($types as $i => $type) { | 
| 232:  | 				if ($type instanceof ClosureType || $type instanceof CallableType || $type instanceof TemplateUnionType) { | 
| 233:  | 					$typeNames[] = sprintf('(%s)', $type->describe($level)); | 
| 234:  | 				} elseif ($type instanceof TemplateType) { | 
| 235:  | 					$isLast = $i >= count($types) - 1; | 
| 236:  | 					$bound = $type->getBound(); | 
| 237:  | 					if ( | 
| 238:  | 						!$isLast | 
| 239:  | 						&& ($level->isTypeOnly() || $level->isValue()) | 
| 240:  | 						&& !($bound instanceof MixedType && $bound->getSubtractedType() === null && !$bound instanceof TemplateMixedType) | 
| 241:  | 					) { | 
| 242:  | 						$typeNames[] = sprintf('(%s)', $type->describe($level)); | 
| 243:  | 					} else { | 
| 244:  | 						$typeNames[] = $type->describe($level); | 
| 245:  | 					} | 
| 246:  | 				} elseif ($type instanceof IntersectionType) { | 
| 247:  | 					$intersectionDescription = $type->describe($level); | 
| 248:  | 					if (strpos($intersectionDescription, '&') !== false) { | 
| 249:  | 						$typeNames[] = sprintf('(%s)', $type->describe($level)); | 
| 250:  | 					} else { | 
| 251:  | 						$typeNames[] = $intersectionDescription; | 
| 252:  | 					} | 
| 253:  | 				} else { | 
| 254:  | 					$typeNames[] = $type->describe($level); | 
| 255:  | 				} | 
| 256:  | 			} | 
| 257:  |  | 
| 258:  | 			return implode('|', $typeNames); | 
| 259:  | 		}; | 
| 260:  |  | 
| 261:  | 		return $this->cachedDescriptions[$level->getLevelValue()] = $level->handle( | 
| 262:  | 			function () use ($joinTypes): string { | 
| 263:  | 				$types = TypeCombinator::union(...array_map(static function (Type $type): Type { | 
| 264:  | 					if ( | 
| 265:  | 						$type instanceof ConstantType | 
| 266:  | 						&& !$type instanceof ConstantBooleanType | 
| 267:  | 					) { | 
| 268:  | 						return $type->generalize(GeneralizePrecision::lessSpecific()); | 
| 269:  | 					} | 
| 270:  |  | 
| 271:  | 					return $type; | 
| 272:  | 				}, $this->getSortedTypes())); | 
| 273:  |  | 
| 274:  | 				if ($types instanceof UnionType) { | 
| 275:  | 					return $joinTypes($types->getSortedTypes()); | 
| 276:  | 				} | 
| 277:  |  | 
| 278:  | 				return $joinTypes([$types]); | 
| 279:  | 			}, | 
| 280:  | 			fn (): string => $joinTypes($this->getSortedTypes()), | 
| 281:  | 		); | 
| 282:  | 	} | 
| 283:  |  | 
| 284:  | 	 | 
| 285:  |  | 
| 286:  |  | 
| 287:  |  | 
| 288:  | 	private function hasInternal( | 
| 289:  | 		callable $canCallback, | 
| 290:  | 		callable $hasCallback, | 
| 291:  | 	): TrinaryLogic | 
| 292:  | 	{ | 
| 293:  | 		return TrinaryLogic::lazyExtremeIdentity($this->types, static function (Type $type) use ($canCallback, $hasCallback): TrinaryLogic { | 
| 294:  | 			if ($canCallback($type)->no()) { | 
| 295:  | 				return TrinaryLogic::createNo(); | 
| 296:  | 			} | 
| 297:  |  | 
| 298:  | 			return $hasCallback($type); | 
| 299:  | 		}); | 
| 300:  | 	} | 
| 301:  |  | 
| 302:  | 	 | 
| 303:  |  | 
| 304:  |  | 
| 305:  |  | 
| 306:  |  | 
| 307:  |  | 
| 308:  | 	private function getInternal( | 
| 309:  | 		callable $hasCallback, | 
| 310:  | 		callable $getCallback, | 
| 311:  | 	): object | 
| 312:  | 	{ | 
| 313:  | 		 | 
| 314:  | 		$result = null; | 
| 315:  |  | 
| 316:  | 		 | 
| 317:  | 		$object = null; | 
| 318:  | 		foreach ($this->types as $type) { | 
| 319:  | 			$has = $hasCallback($type); | 
| 320:  | 			if (!$has->yes()) { | 
| 321:  | 				continue; | 
| 322:  | 			} | 
| 323:  | 			if ($result !== null && $result->compareTo($has) !== $has) { | 
| 324:  | 				continue; | 
| 325:  | 			} | 
| 326:  |  | 
| 327:  | 			$get = $getCallback($type); | 
| 328:  | 			$result = $has; | 
| 329:  | 			$object = $get; | 
| 330:  | 		} | 
| 331:  |  | 
| 332:  | 		if ($object === null) { | 
| 333:  | 			throw new ShouldNotHappenException(); | 
| 334:  | 		} | 
| 335:  |  | 
| 336:  | 		return $object; | 
| 337:  | 	} | 
| 338:  |  | 
| 339:  | 	public function canAccessProperties(): TrinaryLogic | 
| 340:  | 	{ | 
| 341:  | 		return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->canAccessProperties()); | 
| 342:  | 	} | 
| 343:  |  | 
| 344:  | 	public function hasProperty(string $propertyName): TrinaryLogic | 
| 345:  | 	{ | 
| 346:  | 		return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->hasProperty($propertyName)); | 
| 347:  | 	} | 
| 348:  |  | 
| 349:  | 	public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection | 
| 350:  | 	{ | 
| 351:  | 		return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty(); | 
| 352:  | 	} | 
| 353:  |  | 
| 354:  | 	public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection | 
| 355:  | 	{ | 
| 356:  | 		$propertyPrototypes = []; | 
| 357:  | 		foreach ($this->types as $type) { | 
| 358:  | 			if (!$type->hasProperty($propertyName)->yes()) { | 
| 359:  | 				continue; | 
| 360:  | 			} | 
| 361:  |  | 
| 362:  | 			$propertyPrototypes[] = $type->getUnresolvedPropertyPrototype($propertyName, $scope)->withFechedOnType($this); | 
| 363:  | 		} | 
| 364:  |  | 
| 365:  | 		$propertiesCount = count($propertyPrototypes); | 
| 366:  | 		if ($propertiesCount === 0) { | 
| 367:  | 			throw new ShouldNotHappenException(); | 
| 368:  | 		} | 
| 369:  |  | 
| 370:  | 		if ($propertiesCount === 1) { | 
| 371:  | 			return $propertyPrototypes[0]; | 
| 372:  | 		} | 
| 373:  |  | 
| 374:  | 		return new UnionTypeUnresolvedPropertyPrototypeReflection($propertyName, $propertyPrototypes); | 
| 375:  | 	} | 
| 376:  |  | 
| 377:  | 	public function canCallMethods(): TrinaryLogic | 
| 378:  | 	{ | 
| 379:  | 		return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->canCallMethods()); | 
| 380:  | 	} | 
| 381:  |  | 
| 382:  | 	public function hasMethod(string $methodName): TrinaryLogic | 
| 383:  | 	{ | 
| 384:  | 		return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->hasMethod($methodName)); | 
| 385:  | 	} | 
| 386:  |  | 
| 387:  | 	public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): ExtendedMethodReflection | 
| 388:  | 	{ | 
| 389:  | 		return $this->getUnresolvedMethodPrototype($methodName, $scope)->getTransformedMethod(); | 
| 390:  | 	} | 
| 391:  |  | 
| 392:  | 	public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection | 
| 393:  | 	{ | 
| 394:  | 		$methodPrototypes = []; | 
| 395:  | 		foreach ($this->types as $type) { | 
| 396:  | 			if (!$type->hasMethod($methodName)->yes()) { | 
| 397:  | 				continue; | 
| 398:  | 			} | 
| 399:  |  | 
| 400:  | 			$methodPrototypes[] = $type->getUnresolvedMethodPrototype($methodName, $scope)->withCalledOnType($this); | 
| 401:  | 		} | 
| 402:  |  | 
| 403:  | 		$methodsCount = count($methodPrototypes); | 
| 404:  | 		if ($methodsCount === 0) { | 
| 405:  | 			throw new ShouldNotHappenException(); | 
| 406:  | 		} | 
| 407:  |  | 
| 408:  | 		if ($methodsCount === 1) { | 
| 409:  | 			return $methodPrototypes[0]; | 
| 410:  | 		} | 
| 411:  |  | 
| 412:  | 		return new UnionTypeUnresolvedMethodPrototypeReflection($methodName, $methodPrototypes); | 
| 413:  | 	} | 
| 414:  |  | 
| 415:  | 	public function canAccessConstants(): TrinaryLogic | 
| 416:  | 	{ | 
| 417:  | 		return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->canAccessConstants()); | 
| 418:  | 	} | 
| 419:  |  | 
| 420:  | 	public function hasConstant(string $constantName): TrinaryLogic | 
| 421:  | 	{ | 
| 422:  | 		return $this->hasInternal( | 
| 423:  | 			static fn (Type $type): TrinaryLogic => $type->canAccessConstants(), | 
| 424:  | 			static fn (Type $type): TrinaryLogic => $type->hasConstant($constantName), | 
| 425:  | 		); | 
| 426:  | 	} | 
| 427:  |  | 
| 428:  | 	public function getConstant(string $constantName): ConstantReflection | 
| 429:  | 	{ | 
| 430:  | 		return $this->getInternal( | 
| 431:  | 			static fn (Type $type): TrinaryLogic => $type->hasConstant($constantName), | 
| 432:  | 			static fn (Type $type): ConstantReflection => $type->getConstant($constantName), | 
| 433:  | 		); | 
| 434:  | 	} | 
| 435:  |  | 
| 436:  | 	public function isIterable(): TrinaryLogic | 
| 437:  | 	{ | 
| 438:  | 		return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->isIterable()); | 
| 439:  | 	} | 
| 440:  |  | 
| 441:  | 	public function isIterableAtLeastOnce(): TrinaryLogic | 
| 442:  | 	{ | 
| 443:  | 		return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->isIterableAtLeastOnce()); | 
| 444:  | 	} | 
| 445:  |  | 
| 446:  | 	public function getArraySize(): Type | 
| 447:  | 	{ | 
| 448:  | 		return $this->unionTypes(static fn (Type $type): Type => $type->getArraySize()); | 
| 449:  | 	} | 
| 450:  |  | 
| 451:  | 	public function getIterableKeyType(): Type | 
| 452:  | 	{ | 
| 453:  | 		return $this->unionTypes(static fn (Type $type): Type => $type->getIterableKeyType()); | 
| 454:  | 	} | 
| 455:  |  | 
| 456:  | 	public function getFirstIterableKeyType(): Type | 
| 457:  | 	{ | 
| 458:  | 		return $this->unionTypes(static fn (Type $type): Type => $type->getFirstIterableKeyType()); | 
| 459:  | 	} | 
| 460:  |  | 
| 461:  | 	public function getLastIterableKeyType(): Type | 
| 462:  | 	{ | 
| 463:  | 		return $this->unionTypes(static fn (Type $type): Type => $type->getLastIterableKeyType()); | 
| 464:  | 	} | 
| 465:  |  | 
| 466:  | 	public function getIterableValueType(): Type | 
| 467:  | 	{ | 
| 468:  | 		return $this->unionTypes(static fn (Type $type): Type => $type->getIterableValueType()); | 
| 469:  | 	} | 
| 470:  |  | 
| 471:  | 	public function getFirstIterableValueType(): Type | 
| 472:  | 	{ | 
| 473:  | 		return $this->unionTypes(static fn (Type $type): Type => $type->getFirstIterableValueType()); | 
| 474:  | 	} | 
| 475:  |  | 
| 476:  | 	public function getLastIterableValueType(): Type | 
| 477:  | 	{ | 
| 478:  | 		return $this->unionTypes(static fn (Type $type): Type => $type->getLastIterableValueType()); | 
| 479:  | 	} | 
| 480:  |  | 
| 481:  | 	public function isArray(): TrinaryLogic | 
| 482:  | 	{ | 
| 483:  | 		return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isArray()); | 
| 484:  | 	} | 
| 485:  |  | 
| 486:  | 	public function isConstantArray(): TrinaryLogic | 
| 487:  | 	{ | 
| 488:  | 		return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isConstantArray()); | 
| 489:  | 	} | 
| 490:  |  | 
| 491:  | 	public function isOversizedArray(): TrinaryLogic | 
| 492:  | 	{ | 
| 493:  | 		return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isOversizedArray()); | 
| 494:  | 	} | 
| 495:  |  | 
| 496:  | 	public function isList(): TrinaryLogic | 
| 497:  | 	{ | 
| 498:  | 		return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isList()); | 
| 499:  | 	} | 
| 500:  |  | 
| 501:  | 	public function isString(): TrinaryLogic | 
| 502:  | 	{ | 
| 503:  | 		return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isString()); | 
| 504:  | 	} | 
| 505:  |  | 
| 506:  | 	public function isNumericString(): TrinaryLogic | 
| 507:  | 	{ | 
| 508:  | 		return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isNumericString()); | 
| 509:  | 	} | 
| 510:  |  | 
| 511:  | 	public function isNonEmptyString(): TrinaryLogic | 
| 512:  | 	{ | 
| 513:  | 		return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isNonEmptyString()); | 
| 514:  | 	} | 
| 515:  |  | 
| 516:  | 	public function isNonFalsyString(): TrinaryLogic | 
| 517:  | 	{ | 
| 518:  | 		return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isNonFalsyString()); | 
| 519:  | 	} | 
| 520:  |  | 
| 521:  | 	public function isLiteralString(): TrinaryLogic | 
| 522:  | 	{ | 
| 523:  | 		return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isLiteralString()); | 
| 524:  | 	} | 
| 525:  |  | 
| 526:  | 	public function isClassStringType(): TrinaryLogic | 
| 527:  | 	{ | 
| 528:  | 		return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isClassStringType()); | 
| 529:  | 	} | 
| 530:  |  | 
| 531:  | 	public function isVoid(): TrinaryLogic | 
| 532:  | 	{ | 
| 533:  | 		return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->isVoid()); | 
| 534:  | 	} | 
| 535:  |  | 
| 536:  | 	public function isScalar(): TrinaryLogic | 
| 537:  | 	{ | 
| 538:  | 		return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->isScalar()); | 
| 539:  | 	} | 
| 540:  |  | 
| 541:  | 	public function isOffsetAccessible(): TrinaryLogic | 
| 542:  | 	{ | 
| 543:  | 		return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->isOffsetAccessible()); | 
| 544:  | 	} | 
| 545:  |  | 
| 546:  | 	public function hasOffsetValueType(Type $offsetType): TrinaryLogic | 
| 547:  | 	{ | 
| 548:  | 		return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->hasOffsetValueType($offsetType)); | 
| 549:  | 	} | 
| 550:  |  | 
| 551:  | 	public function getOffsetValueType(Type $offsetType): Type | 
| 552:  | 	{ | 
| 553:  | 		$types = []; | 
| 554:  | 		foreach ($this->types as $innerType) { | 
| 555:  | 			$valueType = $innerType->getOffsetValueType($offsetType); | 
| 556:  | 			if ($valueType instanceof ErrorType) { | 
| 557:  | 				continue; | 
| 558:  | 			} | 
| 559:  |  | 
| 560:  | 			$types[] = $valueType; | 
| 561:  | 		} | 
| 562:  |  | 
| 563:  | 		if (count($types) === 0) { | 
| 564:  | 			return new ErrorType(); | 
| 565:  | 		} | 
| 566:  |  | 
| 567:  | 		return TypeCombinator::union(...$types); | 
| 568:  | 	} | 
| 569:  |  | 
| 570:  | 	public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type | 
| 571:  | 	{ | 
| 572:  | 		return $this->unionTypes(static fn (Type $type): Type => $type->setOffsetValueType($offsetType, $valueType, $unionValues)); | 
| 573:  | 	} | 
| 574:  |  | 
| 575:  | 	public function unsetOffset(Type $offsetType): Type | 
| 576:  | 	{ | 
| 577:  | 		return $this->unionTypes(static fn (Type $type): Type => $type->unsetOffset($offsetType)); | 
| 578:  | 	} | 
| 579:  |  | 
| 580:  | 	public function getKeysArray(): Type | 
| 581:  | 	{ | 
| 582:  | 		return $this->unionTypes(static fn (Type $type): Type => $type->getKeysArray()); | 
| 583:  | 	} | 
| 584:  |  | 
| 585:  | 	public function getValuesArray(): Type | 
| 586:  | 	{ | 
| 587:  | 		return $this->unionTypes(static fn (Type $type): Type => $type->getValuesArray()); | 
| 588:  | 	} | 
| 589:  |  | 
| 590:  | 	public function fillKeysArray(Type $valueType): Type | 
| 591:  | 	{ | 
| 592:  | 		return $this->unionTypes(static fn (Type $type): Type => $type->fillKeysArray($valueType)); | 
| 593:  | 	} | 
| 594:  |  | 
| 595:  | 	public function flipArray(): Type | 
| 596:  | 	{ | 
| 597:  | 		return $this->unionTypes(static fn (Type $type): Type => $type->flipArray()); | 
| 598:  | 	} | 
| 599:  |  | 
| 600:  | 	public function intersectKeyArray(Type $otherArraysType): Type | 
| 601:  | 	{ | 
| 602:  | 		return $this->unionTypes(static fn (Type $type): Type => $type->intersectKeyArray($otherArraysType)); | 
| 603:  | 	} | 
| 604:  |  | 
| 605:  | 	public function popArray(): Type | 
| 606:  | 	{ | 
| 607:  | 		return $this->unionTypes(static fn (Type $type): Type => $type->popArray()); | 
| 608:  | 	} | 
| 609:  |  | 
| 610:  | 	public function searchArray(Type $needleType): Type | 
| 611:  | 	{ | 
| 612:  | 		return $this->unionTypes(static fn (Type $type): Type => $type->searchArray($needleType)); | 
| 613:  | 	} | 
| 614:  |  | 
| 615:  | 	public function shiftArray(): Type | 
| 616:  | 	{ | 
| 617:  | 		return $this->unionTypes(static fn (Type $type): Type => $type->shiftArray()); | 
| 618:  | 	} | 
| 619:  |  | 
| 620:  | 	public function shuffleArray(): Type | 
| 621:  | 	{ | 
| 622:  | 		return $this->unionTypes(static fn (Type $type): Type => $type->shuffleArray()); | 
| 623:  | 	} | 
| 624:  |  | 
| 625:  | 	public function isCallable(): TrinaryLogic | 
| 626:  | 	{ | 
| 627:  | 		return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->isCallable()); | 
| 628:  | 	} | 
| 629:  |  | 
| 630:  | 	 | 
| 631:  |  | 
| 632:  |  | 
| 633:  | 	public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array | 
| 634:  | 	{ | 
| 635:  | 		foreach ($this->types as $type) { | 
| 636:  | 			if ($type->isCallable()->no()) { | 
| 637:  | 				continue; | 
| 638:  | 			} | 
| 639:  |  | 
| 640:  | 			return $type->getCallableParametersAcceptors($scope); | 
| 641:  | 		} | 
| 642:  |  | 
| 643:  | 		throw new ShouldNotHappenException(); | 
| 644:  | 	} | 
| 645:  |  | 
| 646:  | 	public function isCloneable(): TrinaryLogic | 
| 647:  | 	{ | 
| 648:  | 		return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->isCloneable()); | 
| 649:  | 	} | 
| 650:  |  | 
| 651:  | 	public function isSmallerThan(Type $otherType): TrinaryLogic | 
| 652:  | 	{ | 
| 653:  | 		return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isSmallerThan($otherType)); | 
| 654:  | 	} | 
| 655:  |  | 
| 656:  | 	public function isSmallerThanOrEqual(Type $otherType): TrinaryLogic | 
| 657:  | 	{ | 
| 658:  | 		return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isSmallerThanOrEqual($otherType)); | 
| 659:  | 	} | 
| 660:  |  | 
| 661:  | 	public function isNull(): TrinaryLogic | 
| 662:  | 	{ | 
| 663:  | 		return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isNull()); | 
| 664:  | 	} | 
| 665:  |  | 
| 666:  | 	public function isTrue(): TrinaryLogic | 
| 667:  | 	{ | 
| 668:  | 		return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isTrue()); | 
| 669:  | 	} | 
| 670:  |  | 
| 671:  | 	public function isFalse(): TrinaryLogic | 
| 672:  | 	{ | 
| 673:  | 		return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isFalse()); | 
| 674:  | 	} | 
| 675:  |  | 
| 676:  | 	public function isBoolean(): TrinaryLogic | 
| 677:  | 	{ | 
| 678:  | 		return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isBoolean()); | 
| 679:  | 	} | 
| 680:  |  | 
| 681:  | 	public function isFloat(): TrinaryLogic | 
| 682:  | 	{ | 
| 683:  | 		return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isFloat()); | 
| 684:  | 	} | 
| 685:  |  | 
| 686:  | 	public function isInteger(): TrinaryLogic | 
| 687:  | 	{ | 
| 688:  | 		return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isInteger()); | 
| 689:  | 	} | 
| 690:  |  | 
| 691:  | 	public function getSmallerType(): Type | 
| 692:  | 	{ | 
| 693:  | 		return $this->unionTypes(static fn (Type $type): Type => $type->getSmallerType()); | 
| 694:  | 	} | 
| 695:  |  | 
| 696:  | 	public function getSmallerOrEqualType(): Type | 
| 697:  | 	{ | 
| 698:  | 		return $this->unionTypes(static fn (Type $type): Type => $type->getSmallerOrEqualType()); | 
| 699:  | 	} | 
| 700:  |  | 
| 701:  | 	public function getGreaterType(): Type | 
| 702:  | 	{ | 
| 703:  | 		return $this->unionTypes(static fn (Type $type): Type => $type->getGreaterType()); | 
| 704:  | 	} | 
| 705:  |  | 
| 706:  | 	public function getGreaterOrEqualType(): Type | 
| 707:  | 	{ | 
| 708:  | 		return $this->unionTypes(static fn (Type $type): Type => $type->getGreaterOrEqualType()); | 
| 709:  | 	} | 
| 710:  |  | 
| 711:  | 	public function isGreaterThan(Type $otherType): TrinaryLogic | 
| 712:  | 	{ | 
| 713:  | 		return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $otherType->isSmallerThan($type)); | 
| 714:  | 	} | 
| 715:  |  | 
| 716:  | 	public function isGreaterThanOrEqual(Type $otherType): TrinaryLogic | 
| 717:  | 	{ | 
| 718:  | 		return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $otherType->isSmallerThanOrEqual($type)); | 
| 719:  | 	} | 
| 720:  |  | 
| 721:  | 	public function toBoolean(): BooleanType | 
| 722:  | 	{ | 
| 723:  | 		 | 
| 724:  | 		$type = $this->unionTypes(static fn (Type $type): BooleanType => $type->toBoolean()); | 
| 725:  |  | 
| 726:  | 		return $type; | 
| 727:  | 	} | 
| 728:  |  | 
| 729:  | 	public function toNumber(): Type | 
| 730:  | 	{ | 
| 731:  | 		$type = $this->unionTypes(static fn (Type $type): Type => $type->toNumber()); | 
| 732:  |  | 
| 733:  | 		return $type; | 
| 734:  | 	} | 
| 735:  |  | 
| 736:  | 	public function toString(): Type | 
| 737:  | 	{ | 
| 738:  | 		$type = $this->unionTypes(static fn (Type $type): Type => $type->toString()); | 
| 739:  |  | 
| 740:  | 		return $type; | 
| 741:  | 	} | 
| 742:  |  | 
| 743:  | 	public function toInteger(): Type | 
| 744:  | 	{ | 
| 745:  | 		$type = $this->unionTypes(static fn (Type $type): Type => $type->toInteger()); | 
| 746:  |  | 
| 747:  | 		return $type; | 
| 748:  | 	} | 
| 749:  |  | 
| 750:  | 	public function toFloat(): Type | 
| 751:  | 	{ | 
| 752:  | 		$type = $this->unionTypes(static fn (Type $type): Type => $type->toFloat()); | 
| 753:  |  | 
| 754:  | 		return $type; | 
| 755:  | 	} | 
| 756:  |  | 
| 757:  | 	public function toArray(): Type | 
| 758:  | 	{ | 
| 759:  | 		$type = $this->unionTypes(static fn (Type $type): Type => $type->toArray()); | 
| 760:  |  | 
| 761:  | 		return $type; | 
| 762:  | 	} | 
| 763:  |  | 
| 764:  | 	public function toArrayKey(): Type | 
| 765:  | 	{ | 
| 766:  | 		return $this->unionTypes(static fn (Type $type): Type => $type->toArrayKey()); | 
| 767:  | 	} | 
| 768:  |  | 
| 769:  | 	public function inferTemplateTypes(Type $receivedType): TemplateTypeMap | 
| 770:  | 	{ | 
| 771:  | 		$types = TemplateTypeMap::createEmpty(); | 
| 772:  | 		if ($receivedType instanceof UnionType) { | 
| 773:  | 			$myTypes = []; | 
| 774:  | 			$remainingReceivedTypes = []; | 
| 775:  | 			foreach ($receivedType->getTypes() as $receivedInnerType) { | 
| 776:  | 				foreach ($this->types as $type) { | 
| 777:  | 					if ($type->isSuperTypeOf($receivedInnerType)->yes()) { | 
| 778:  | 						$types = $types->union($type->inferTemplateTypes($receivedInnerType)); | 
| 779:  | 						continue 2; | 
| 780:  | 					} | 
| 781:  | 					$myTypes[] = $type; | 
| 782:  | 				} | 
| 783:  | 				$remainingReceivedTypes[] = $receivedInnerType; | 
| 784:  | 			} | 
| 785:  | 			if (count($remainingReceivedTypes) === 0) { | 
| 786:  | 				return $types; | 
| 787:  | 			} | 
| 788:  | 			$receivedType = TypeCombinator::union(...$remainingReceivedTypes); | 
| 789:  | 		} else { | 
| 790:  | 			$myTypes = $this->types; | 
| 791:  | 		} | 
| 792:  |  | 
| 793:  | 		$myTemplateTypes = []; | 
| 794:  | 		foreach ($myTypes as $type) { | 
| 795:  | 			if ($type instanceof TemplateType || ($type instanceof GenericClassStringType && $type->getGenericType() instanceof TemplateType)) { | 
| 796:  | 				$myTemplateTypes[] = $type; | 
| 797:  | 				continue; | 
| 798:  | 			} | 
| 799:  | 			$types = $types->union($type->inferTemplateTypes($receivedType)); | 
| 800:  | 		} | 
| 801:  |  | 
| 802:  | 		if (!$types->isEmpty()) { | 
| 803:  | 			return $types; | 
| 804:  | 		} | 
| 805:  |  | 
| 806:  | 		foreach ($myTypes as $type) { | 
| 807:  | 			$types = $types->union($type->inferTemplateTypes($receivedType)); | 
| 808:  | 		} | 
| 809:  |  | 
| 810:  | 		return $types; | 
| 811:  | 	} | 
| 812:  |  | 
| 813:  | 	public function inferTemplateTypesOn(Type $templateType): TemplateTypeMap | 
| 814:  | 	{ | 
| 815:  | 		$types = TemplateTypeMap::createEmpty(); | 
| 816:  |  | 
| 817:  | 		foreach ($this->types as $type) { | 
| 818:  | 			$types = $types->union($templateType->inferTemplateTypes($type)); | 
| 819:  | 		} | 
| 820:  |  | 
| 821:  | 		return $types; | 
| 822:  | 	} | 
| 823:  |  | 
| 824:  | 	public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array | 
| 825:  | 	{ | 
| 826:  | 		$references = []; | 
| 827:  |  | 
| 828:  | 		foreach ($this->types as $type) { | 
| 829:  | 			foreach ($type->getReferencedTemplateTypes($positionVariance) as $reference) { | 
| 830:  | 				$references[] = $reference; | 
| 831:  | 			} | 
| 832:  | 		} | 
| 833:  |  | 
| 834:  | 		return $references; | 
| 835:  | 	} | 
| 836:  |  | 
| 837:  | 	public function traverse(callable $cb): Type | 
| 838:  | 	{ | 
| 839:  | 		$types = []; | 
| 840:  | 		$changed = false; | 
| 841:  |  | 
| 842:  | 		foreach ($this->types as $type) { | 
| 843:  | 			$newType = $cb($type); | 
| 844:  | 			if ($type !== $newType) { | 
| 845:  | 				$changed = true; | 
| 846:  | 			} | 
| 847:  | 			$types[] = $newType; | 
| 848:  | 		} | 
| 849:  |  | 
| 850:  | 		if ($changed) { | 
| 851:  | 			return TypeCombinator::union(...$types); | 
| 852:  | 		} | 
| 853:  |  | 
| 854:  | 		return $this; | 
| 855:  | 	} | 
| 856:  |  | 
| 857:  | 	public function tryRemove(Type $typeToRemove): ?Type | 
| 858:  | 	{ | 
| 859:  | 		return $this->unionTypes(static fn (Type $type): Type => TypeCombinator::remove($type, $typeToRemove)); | 
| 860:  | 	} | 
| 861:  |  | 
| 862:  | 	 | 
| 863:  |  | 
| 864:  |  | 
| 865:  | 	public static function __set_state(array $properties): Type | 
| 866:  | 	{ | 
| 867:  | 		return new self($properties['types'], $properties['normalized']); | 
| 868:  | 	} | 
| 869:  |  | 
| 870:  | 	 | 
| 871:  |  | 
| 872:  |  | 
| 873:  | 	protected function unionResults(callable $getResult): TrinaryLogic | 
| 874:  | 	{ | 
| 875:  | 		return TrinaryLogic::lazyExtremeIdentity($this->types, $getResult); | 
| 876:  | 	} | 
| 877:  |  | 
| 878:  | 	 | 
| 879:  |  | 
| 880:  |  | 
| 881:  | 	private function notBenevolentUnionResults(callable $getResult): TrinaryLogic | 
| 882:  | 	{ | 
| 883:  | 		return TrinaryLogic::lazyExtremeIdentity($this->types, $getResult); | 
| 884:  | 	} | 
| 885:  |  | 
| 886:  | 	 | 
| 887:  |  | 
| 888:  |  | 
| 889:  | 	protected function unionTypes(callable $getType): Type | 
| 890:  | 	{ | 
| 891:  | 		return TypeCombinator::union(...array_map($getType, $this->types)); | 
| 892:  | 	} | 
| 893:  |  | 
| 894:  | 	 | 
| 895:  |  | 
| 896:  |  | 
| 897:  |  | 
| 898:  |  | 
| 899:  | 	protected function pickTypes(callable $getTypes): array | 
| 900:  | 	{ | 
| 901:  | 		$types = []; | 
| 902:  | 		foreach ($this->types as $type) { | 
| 903:  | 			$innerTypes = $getTypes($type); | 
| 904:  | 			if ($innerTypes === []) { | 
| 905:  | 				return []; | 
| 906:  | 			} | 
| 907:  |  | 
| 908:  | 			foreach ($innerTypes as $innerType) { | 
| 909:  | 				$types[] = $innerType; | 
| 910:  | 			} | 
| 911:  | 		} | 
| 912:  |  | 
| 913:  | 		return $types; | 
| 914:  | 	} | 
| 915:  |  | 
| 916:  | } | 
| 917:  |  |