|    1:  | <?php declare(strict_types = 1); | 
|    2:  |  | 
|    3:  | namespace PHPStan\Type; | 
|    4:  |  | 
|    5:  | use ArrayAccess; | 
|    6:  | use Closure; | 
|    7:  | use Countable; | 
|    8:  | use Iterator; | 
|    9:  | use IteratorAggregate; | 
|   10:  | use PHPStan\Analyser\OutOfClassScope; | 
|   11:  | use PHPStan\Broker\ClassNotFoundException; | 
|   12:  | use PHPStan\Php\PhpVersion; | 
|   13:  | use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; | 
|   14:  | use PHPStan\PhpDocParser\Ast\Type\TypeNode; | 
|   15:  | use PHPStan\Reflection\Callables\CallableParametersAcceptor; | 
|   16:  | use PHPStan\Reflection\Callables\FunctionCallableVariant; | 
|   17:  | use PHPStan\Reflection\ClassConstantReflection; | 
|   18:  | use PHPStan\Reflection\ClassMemberAccessAnswerer; | 
|   19:  | use PHPStan\Reflection\ClassReflection; | 
|   20:  | use PHPStan\Reflection\ExtendedMethodReflection; | 
|   21:  | use PHPStan\Reflection\ExtendedPropertyReflection; | 
|   22:  | use PHPStan\Reflection\Php\UniversalObjectCratesClassReflectionExtension; | 
|   23:  | use PHPStan\Reflection\PropertyReflection; | 
|   24:  | use PHPStan\Reflection\ReflectionProviderStaticAccessor; | 
|   25:  | use PHPStan\Reflection\TrivialParametersAcceptor; | 
|   26:  | use PHPStan\Reflection\Type\CalledOnTypeUnresolvedMethodPrototypeReflection; | 
|   27:  | use PHPStan\Reflection\Type\CalledOnTypeUnresolvedPropertyPrototypeReflection; | 
|   28:  | use PHPStan\Reflection\Type\UnionTypeUnresolvedPropertyPrototypeReflection; | 
|   29:  | use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection; | 
|   30:  | use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection; | 
|   31:  | use PHPStan\ShouldNotHappenException; | 
|   32:  | use PHPStan\TrinaryLogic; | 
|   33:  | use PHPStan\Type\Constant\ConstantArrayType; | 
|   34:  | use PHPStan\Type\Constant\ConstantBooleanType; | 
|   35:  | use PHPStan\Type\Constant\ConstantStringType; | 
|   36:  | use PHPStan\Type\Enum\EnumCaseObjectType; | 
|   37:  | use PHPStan\Type\Generic\GenericObjectType; | 
|   38:  | use PHPStan\Type\Generic\TemplateTypeHelper; | 
|   39:  | use PHPStan\Type\Traits\MaybeIterableTypeTrait; | 
|   40:  | use PHPStan\Type\Traits\NonArrayTypeTrait; | 
|   41:  | use PHPStan\Type\Traits\NonGeneralizableTypeTrait; | 
|   42:  | use PHPStan\Type\Traits\NonGenericTypeTrait; | 
|   43:  | use PHPStan\Type\Traits\UndecidedComparisonTypeTrait; | 
|   44:  | use Traversable; | 
|   45:  | use function array_key_exists; | 
|   46:  | use function array_map; | 
|   47:  | use function array_values; | 
|   48:  | use function count; | 
|   49:  | use function implode; | 
|   50:  | use function in_array; | 
|   51:  | use function sprintf; | 
|   52:  | use function strtolower; | 
|   53:  |  | 
|   54:  |  | 
|   55:  | class ObjectType implements TypeWithClassName, SubtractableType | 
|   56:  | { | 
|   57:  |  | 
|   58:  | 	use MaybeIterableTypeTrait; | 
|   59:  | 	use NonArrayTypeTrait; | 
|   60:  | 	use NonGenericTypeTrait; | 
|   61:  | 	use UndecidedComparisonTypeTrait; | 
|   62:  | 	use NonGeneralizableTypeTrait; | 
|   63:  |  | 
|   64:  | 	private const EXTRA_OFFSET_CLASSES = [ | 
|   65:  | 		'DOMNamedNodeMap',  | 
|   66:  | 		'Dom\NamedNodeMap',  | 
|   67:  | 		'DOMNodeList',  | 
|   68:  | 		'Dom\NodeList',  | 
|   69:  | 		'Dom\HTMLCollection',  | 
|   70:  | 		'Dom\DtdNamedNodeMap',  | 
|   71:  | 		'PDORow',  | 
|   72:  | 		'ResourceBundle',  | 
|   73:  | 		'FFI\CData',  | 
|   74:  | 		'SimpleXMLElement', | 
|   75:  | 		'Threaded', | 
|   76:  | 	]; | 
|   77:  |  | 
|   78:  | 	private ?Type $subtractedType; | 
|   79:  |  | 
|   80:  | 	 | 
|   81:  | 	private static array $superTypes = []; | 
|   82:  |  | 
|   83:  | 	private ?self $cachedParent = null; | 
|   84:  |  | 
|   85:  | 	 | 
|   86:  | 	private ?array $cachedInterfaces = null; | 
|   87:  |  | 
|   88:  | 	 | 
|   89:  | 	private static array $methods = []; | 
|   90:  |  | 
|   91:  | 	 | 
|   92:  | 	private static array $properties = []; | 
|   93:  |  | 
|   94:  | 	 | 
|   95:  | 	private static array $ancestors = []; | 
|   96:  |  | 
|   97:  | 	 | 
|   98:  | 	private array $currentAncestors = []; | 
|   99:  |  | 
|  100:  | 	private ?string $cachedDescription = null; | 
|  101:  |  | 
|  102:  | 	 | 
|  103:  | 	private static array $enumCases = []; | 
|  104:  |  | 
|  105:  | 	 | 
|  106:  | 	public function __construct( | 
|  107:  | 		private string $className, | 
|  108:  | 		?Type $subtractedType = null, | 
|  109:  | 		private ?ClassReflection $classReflection = null, | 
|  110:  | 	) | 
|  111:  | 	{ | 
|  112:  | 		if ($subtractedType instanceof NeverType) { | 
|  113:  | 			$subtractedType = null; | 
|  114:  | 		} | 
|  115:  |  | 
|  116:  | 		$this->subtractedType = $subtractedType; | 
|  117:  | 	} | 
|  118:  |  | 
|  119:  | 	public static function resetCaches(): void | 
|  120:  | 	{ | 
|  121:  | 		self::$superTypes = []; | 
|  122:  | 		self::$methods = []; | 
|  123:  | 		self::$properties = []; | 
|  124:  | 		self::$ancestors = []; | 
|  125:  | 		self::$enumCases = []; | 
|  126:  | 	} | 
|  127:  |  | 
|  128:  | 	private static function createFromReflection(ClassReflection $reflection): self | 
|  129:  | 	{ | 
|  130:  | 		if (!$reflection->isGeneric()) { | 
|  131:  | 			return new ObjectType($reflection->getName()); | 
|  132:  | 		} | 
|  133:  |  | 
|  134:  | 		return new GenericObjectType( | 
|  135:  | 			$reflection->getName(), | 
|  136:  | 			$reflection->typeMapToList($reflection->getActiveTemplateTypeMap()), | 
|  137:  | 			null, | 
|  138:  | 			null, | 
|  139:  | 			$reflection->varianceMapToList($reflection->getCallSiteVarianceMap()), | 
|  140:  | 		); | 
|  141:  | 	} | 
|  142:  |  | 
|  143:  | 	public function getClassName(): string | 
|  144:  | 	{ | 
|  145:  | 		return $this->className; | 
|  146:  | 	} | 
|  147:  |  | 
|  148:  | 	public function hasProperty(string $propertyName): TrinaryLogic | 
|  149:  | 	{ | 
|  150:  | 		$classReflection = $this->getClassReflection(); | 
|  151:  | 		if ($classReflection === null) { | 
|  152:  | 			return TrinaryLogic::createMaybe(); | 
|  153:  | 		} | 
|  154:  |  | 
|  155:  | 		if ($classReflection->hasProperty($propertyName)) { | 
|  156:  | 			return TrinaryLogic::createYes(); | 
|  157:  | 		} | 
|  158:  |  | 
|  159:  | 		if ($classReflection->allowsDynamicProperties()) { | 
|  160:  | 			return TrinaryLogic::createMaybe(); | 
|  161:  | 		} | 
|  162:  |  | 
|  163:  | 		if (!$classReflection->isFinal()) { | 
|  164:  | 			return TrinaryLogic::createMaybe(); | 
|  165:  | 		} | 
|  166:  |  | 
|  167:  | 		return TrinaryLogic::createNo(); | 
|  168:  | 	} | 
|  169:  |  | 
|  170:  | 	public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection | 
|  171:  | 	{ | 
|  172:  | 		return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty(); | 
|  173:  | 	} | 
|  174:  |  | 
|  175:  | 	public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection | 
|  176:  | 	{ | 
|  177:  | 		if (!$scope->isInClass()) { | 
|  178:  | 			$canAccessProperty = 'no'; | 
|  179:  | 		} else { | 
|  180:  | 			$canAccessProperty = $scope->getClassReflection()->getName(); | 
|  181:  | 		} | 
|  182:  | 		$description = $this->describeCache(); | 
|  183:  |  | 
|  184:  | 		if (isset(self::$properties[$description][$propertyName][$canAccessProperty])) { | 
|  185:  | 			return self::$properties[$description][$propertyName][$canAccessProperty]; | 
|  186:  | 		} | 
|  187:  |  | 
|  188:  | 		$nakedClassReflection = $this->getNakedClassReflection(); | 
|  189:  | 		if ($nakedClassReflection === null) { | 
|  190:  | 			throw new ClassNotFoundException($this->className); | 
|  191:  | 		} | 
|  192:  |  | 
|  193:  | 		if ($nakedClassReflection->isEnum()) { | 
|  194:  | 			if ( | 
|  195:  | 				$propertyName === 'name' | 
|  196:  | 				|| ($propertyName === 'value' && $nakedClassReflection->isBackedEnum()) | 
|  197:  | 			) { | 
|  198:  | 				$properties = []; | 
|  199:  | 				foreach ($this->getEnumCases() as $enumCase) { | 
|  200:  | 					$properties[] = $enumCase->getUnresolvedPropertyPrototype($propertyName, $scope); | 
|  201:  | 				} | 
|  202:  |  | 
|  203:  | 				if (count($properties) > 0) { | 
|  204:  | 					if (count($properties) === 1) { | 
|  205:  | 						return $properties[0]; | 
|  206:  | 					} | 
|  207:  |  | 
|  208:  | 					return new UnionTypeUnresolvedPropertyPrototypeReflection($propertyName, $properties); | 
|  209:  | 				} | 
|  210:  | 			} | 
|  211:  | 		} | 
|  212:  |  | 
|  213:  | 		if (!$nakedClassReflection->hasNativeProperty($propertyName)) { | 
|  214:  | 			$nakedClassReflection = $this->getClassReflection(); | 
|  215:  | 		} | 
|  216:  |  | 
|  217:  | 		if ($nakedClassReflection === null) { | 
|  218:  | 			throw new ClassNotFoundException($this->className); | 
|  219:  | 		} | 
|  220:  |  | 
|  221:  | 		$property = $nakedClassReflection->getProperty($propertyName, $scope); | 
|  222:  |  | 
|  223:  | 		$ancestor = $this->getAncestorWithClassName($property->getDeclaringClass()->getName()); | 
|  224:  | 		$resolvedClassReflection = null; | 
|  225:  | 		if ($ancestor !== null && $ancestor->hasProperty($propertyName)->yes()) { | 
|  226:  | 			$resolvedClassReflection = $ancestor->getClassReflection(); | 
|  227:  | 			if ($ancestor !== $this) { | 
|  228:  | 				$property = $ancestor->getUnresolvedPropertyPrototype($propertyName, $scope)->getNakedProperty(); | 
|  229:  | 			} | 
|  230:  | 		} | 
|  231:  | 		if ($resolvedClassReflection === null) { | 
|  232:  | 			$resolvedClassReflection = $property->getDeclaringClass(); | 
|  233:  | 		} | 
|  234:  |  | 
|  235:  | 		return self::$properties[$description][$propertyName][$canAccessProperty] = new CalledOnTypeUnresolvedPropertyPrototypeReflection( | 
|  236:  | 			$property, | 
|  237:  | 			$resolvedClassReflection, | 
|  238:  | 			true, | 
|  239:  | 			$this, | 
|  240:  | 		); | 
|  241:  | 	} | 
|  242:  |  | 
|  243:  | 	public function getPropertyWithoutTransformingStatic(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection | 
|  244:  | 	{ | 
|  245:  | 		$classReflection = $this->getNakedClassReflection(); | 
|  246:  | 		if ($classReflection === null) { | 
|  247:  | 			throw new ClassNotFoundException($this->className); | 
|  248:  | 		} | 
|  249:  |  | 
|  250:  | 		if (!$classReflection->hasProperty($propertyName)) { | 
|  251:  | 			$classReflection = $this->getClassReflection(); | 
|  252:  | 		} | 
|  253:  |  | 
|  254:  | 		if ($classReflection === null) { | 
|  255:  | 			throw new ClassNotFoundException($this->className); | 
|  256:  | 		} | 
|  257:  |  | 
|  258:  | 		return $classReflection->getProperty($propertyName, $scope); | 
|  259:  | 	} | 
|  260:  |  | 
|  261:  | 	public function getReferencedClasses(): array | 
|  262:  | 	{ | 
|  263:  | 		return [$this->className]; | 
|  264:  | 	} | 
|  265:  |  | 
|  266:  | 	public function getObjectClassNames(): array | 
|  267:  | 	{ | 
|  268:  | 		if ($this->className === '') { | 
|  269:  | 			return []; | 
|  270:  | 		} | 
|  271:  | 		return [$this->className]; | 
|  272:  | 	} | 
|  273:  |  | 
|  274:  | 	public function getObjectClassReflections(): array | 
|  275:  | 	{ | 
|  276:  | 		$classReflection = $this->getClassReflection(); | 
|  277:  | 		if ($classReflection === null) { | 
|  278:  | 			return []; | 
|  279:  | 		} | 
|  280:  |  | 
|  281:  | 		return [$classReflection]; | 
|  282:  | 	} | 
|  283:  |  | 
|  284:  | 	public function accepts(Type $type, bool $strictTypes): AcceptsResult | 
|  285:  | 	{ | 
|  286:  | 		if ($type instanceof StaticType) { | 
|  287:  | 			return $this->checkSubclassAcceptability($type->getClassName()); | 
|  288:  | 		} | 
|  289:  |  | 
|  290:  | 		if ($type instanceof CompoundType) { | 
|  291:  | 			return $type->isAcceptedBy($this, $strictTypes); | 
|  292:  | 		} | 
|  293:  |  | 
|  294:  | 		if ($type instanceof ClosureType) { | 
|  295:  | 			return new AcceptsResult($this->isInstanceOf(Closure::class), []); | 
|  296:  | 		} | 
|  297:  |  | 
|  298:  | 		if ($type instanceof ObjectWithoutClassType) { | 
|  299:  | 			return AcceptsResult::createMaybe(); | 
|  300:  | 		} | 
|  301:  |  | 
|  302:  | 		$thatClassNames = $type->getObjectClassNames(); | 
|  303:  | 		if (count($thatClassNames) > 1) { | 
|  304:  | 			throw new ShouldNotHappenException(); | 
|  305:  | 		} | 
|  306:  |  | 
|  307:  | 		if ($thatClassNames === []) { | 
|  308:  | 			return AcceptsResult::createNo(); | 
|  309:  | 		} | 
|  310:  |  | 
|  311:  | 		return $this->checkSubclassAcceptability($thatClassNames[0]); | 
|  312:  | 	} | 
|  313:  |  | 
|  314:  | 	public function isSuperTypeOf(Type $type): IsSuperTypeOfResult | 
|  315:  | 	{ | 
|  316:  | 		$thatClassNames = $type->getObjectClassNames(); | 
|  317:  | 		if (!$type instanceof CompoundType && $thatClassNames === [] && !$type instanceof ObjectWithoutClassType) { | 
|  318:  | 			return IsSuperTypeOfResult::createNo(); | 
|  319:  | 		} | 
|  320:  |  | 
|  321:  | 		$thisDescription = $this->describeCache(); | 
|  322:  |  | 
|  323:  | 		if ($type instanceof self) { | 
|  324:  | 			$description = $type->describeCache(); | 
|  325:  | 		} else { | 
|  326:  | 			$description = $type->describe(VerbosityLevel::cache()); | 
|  327:  | 		} | 
|  328:  |  | 
|  329:  | 		if (isset(self::$superTypes[$thisDescription][$description])) { | 
|  330:  | 			return self::$superTypes[$thisDescription][$description]; | 
|  331:  | 		} | 
|  332:  |  | 
|  333:  | 		if ($type instanceof CompoundType) { | 
|  334:  | 			return self::$superTypes[$thisDescription][$description] = $type->isSubTypeOf($this); | 
|  335:  | 		} | 
|  336:  |  | 
|  337:  | 		if ($type instanceof ClosureType) { | 
|  338:  | 			return self::$superTypes[$thisDescription][$description] = new IsSuperTypeOfResult($this->isInstanceOf(Closure::class), []); | 
|  339:  | 		} | 
|  340:  |  | 
|  341:  | 		if ($type instanceof ObjectWithoutClassType) { | 
|  342:  | 			if ($type->getSubtractedType() !== null) { | 
|  343:  | 				$isSuperType = $type->getSubtractedType()->isSuperTypeOf($this); | 
|  344:  | 				if ($isSuperType->yes()) { | 
|  345:  | 					return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createNo(); | 
|  346:  | 				} | 
|  347:  | 			} | 
|  348:  | 			return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createMaybe(); | 
|  349:  | 		} | 
|  350:  |  | 
|  351:  | 		$transformResult = static fn (IsSuperTypeOfResult $result) => $result; | 
|  352:  | 		if ($this->subtractedType !== null) { | 
|  353:  | 			$isSuperType = $this->subtractedType->isSuperTypeOf($type); | 
|  354:  | 			if ($isSuperType->yes()) { | 
|  355:  | 				return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createNo(); | 
|  356:  | 			} | 
|  357:  | 			if ($isSuperType->maybe()) { | 
|  358:  | 				$transformResult = static fn (IsSuperTypeOfResult $result) => $result->and(IsSuperTypeOfResult::createMaybe()); | 
|  359:  | 			} | 
|  360:  | 		} | 
|  361:  |  | 
|  362:  | 		if ( | 
|  363:  | 			$type instanceof SubtractableType | 
|  364:  | 			&& $type->getSubtractedType() !== null | 
|  365:  | 		) { | 
|  366:  | 			$isSuperType = $type->getSubtractedType()->isSuperTypeOf($this); | 
|  367:  | 			if ($isSuperType->yes()) { | 
|  368:  | 				return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createNo(); | 
|  369:  | 			} | 
|  370:  | 		} | 
|  371:  |  | 
|  372:  | 		$thisClassName = $this->className; | 
|  373:  | 		if (count($thatClassNames) > 1) { | 
|  374:  | 			throw new ShouldNotHappenException(); | 
|  375:  | 		} | 
|  376:  |  | 
|  377:  | 		if ($thatClassNames[0] === $thisClassName) { | 
|  378:  | 			return $transformResult(IsSuperTypeOfResult::createYes()); | 
|  379:  | 		} | 
|  380:  |  | 
|  381:  | 		$reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); | 
|  382:  | 		$thisClassReflection = $this->getClassReflection(); | 
|  383:  |  | 
|  384:  | 		if ($thisClassReflection === null || !$reflectionProvider->hasClass($thatClassNames[0])) { | 
|  385:  | 			return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createMaybe(); | 
|  386:  | 		} | 
|  387:  |  | 
|  388:  | 		$thatClassReflection = $reflectionProvider->getClass($thatClassNames[0]); | 
|  389:  |  | 
|  390:  | 		if ($thisClassReflection->isTrait() || $thatClassReflection->isTrait()) { | 
|  391:  | 			return IsSuperTypeOfResult::createNo(); | 
|  392:  | 		} | 
|  393:  |  | 
|  394:  | 		if ($thisClassReflection->getName() === $thatClassReflection->getName()) { | 
|  395:  | 			return self::$superTypes[$thisDescription][$description] = $transformResult(IsSuperTypeOfResult::createYes()); | 
|  396:  | 		} | 
|  397:  |  | 
|  398:  | 		if ($thatClassReflection->isSubclassOf($thisClassName)) { | 
|  399:  | 			return self::$superTypes[$thisDescription][$description] = $transformResult(IsSuperTypeOfResult::createYes()); | 
|  400:  | 		} | 
|  401:  |  | 
|  402:  | 		if ($thisClassReflection->isSubclassOf($thatClassNames[0])) { | 
|  403:  | 			return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createMaybe(); | 
|  404:  | 		} | 
|  405:  |  | 
|  406:  | 		if ($thisClassReflection->isInterface() && !$thatClassReflection->getNativeReflection()->isFinal()) { | 
|  407:  | 			return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createMaybe(); | 
|  408:  | 		} | 
|  409:  |  | 
|  410:  | 		if ($thatClassReflection->isInterface() && !$thisClassReflection->getNativeReflection()->isFinal()) { | 
|  411:  | 			return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createMaybe(); | 
|  412:  | 		} | 
|  413:  |  | 
|  414:  | 		return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createNo(); | 
|  415:  | 	} | 
|  416:  |  | 
|  417:  | 	public function equals(Type $type): bool | 
|  418:  | 	{ | 
|  419:  | 		if (!$type instanceof self) { | 
|  420:  | 			return false; | 
|  421:  | 		} | 
|  422:  |  | 
|  423:  | 		if ($type instanceof EnumCaseObjectType) { | 
|  424:  | 			return false; | 
|  425:  | 		} | 
|  426:  |  | 
|  427:  | 		if ($this->className !== $type->className) { | 
|  428:  | 			return false; | 
|  429:  | 		} | 
|  430:  |  | 
|  431:  | 		if ($this->subtractedType === null) { | 
|  432:  | 			return $type->subtractedType === null; | 
|  433:  | 		} | 
|  434:  |  | 
|  435:  | 		if ($type->subtractedType === null) { | 
|  436:  | 			return false; | 
|  437:  | 		} | 
|  438:  |  | 
|  439:  | 		return $this->subtractedType->equals($type->subtractedType); | 
|  440:  | 	} | 
|  441:  |  | 
|  442:  | 	private function checkSubclassAcceptability(string $thatClass): AcceptsResult | 
|  443:  | 	{ | 
|  444:  | 		if ($this->className === $thatClass) { | 
|  445:  | 			return AcceptsResult::createYes(); | 
|  446:  | 		} | 
|  447:  |  | 
|  448:  | 		$reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); | 
|  449:  |  | 
|  450:  | 		if ($this->getClassReflection() === null || !$reflectionProvider->hasClass($thatClass)) { | 
|  451:  | 			return AcceptsResult::createNo(); | 
|  452:  | 		} | 
|  453:  |  | 
|  454:  | 		$thisReflection = $this->getClassReflection(); | 
|  455:  | 		$thatReflection = $reflectionProvider->getClass($thatClass); | 
|  456:  |  | 
|  457:  | 		if ($thisReflection->getName() === $thatReflection->getName()) { | 
|  458:  | 			 | 
|  459:  | 			return AcceptsResult::createYes(); | 
|  460:  | 		} | 
|  461:  |  | 
|  462:  | 		if ($thisReflection->isInterface() && $thatReflection->isInterface()) { | 
|  463:  | 			return AcceptsResult::createFromBoolean( | 
|  464:  | 				$thatReflection->implementsInterface($thisReflection->getName()), | 
|  465:  | 			); | 
|  466:  | 		} | 
|  467:  |  | 
|  468:  | 		return AcceptsResult::createFromBoolean( | 
|  469:  | 			$thatReflection->isSubclassOf($thisReflection->getName()), | 
|  470:  | 		); | 
|  471:  | 	} | 
|  472:  |  | 
|  473:  | 	public function describe(VerbosityLevel $level): string | 
|  474:  | 	{ | 
|  475:  | 		$preciseNameCallback = function (): string { | 
|  476:  | 			$reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); | 
|  477:  | 			if (!$reflectionProvider->hasClass($this->className)) { | 
|  478:  | 				return $this->className; | 
|  479:  | 			} | 
|  480:  |  | 
|  481:  | 			return $reflectionProvider->getClassName($this->className); | 
|  482:  | 		}; | 
|  483:  |  | 
|  484:  | 		$preciseWithSubtracted = function () use ($level): string { | 
|  485:  | 			$description = $this->className; | 
|  486:  | 			if ($this->subtractedType !== null) { | 
|  487:  | 				$description .= $this->subtractedType instanceof UnionType | 
|  488:  | 					? sprintf('~(%s)', $this->subtractedType->describe($level)) | 
|  489:  | 					: sprintf('~%s', $this->subtractedType->describe($level)); | 
|  490:  | 			} | 
|  491:  |  | 
|  492:  | 			return $description; | 
|  493:  | 		}; | 
|  494:  |  | 
|  495:  | 		return $level->handle( | 
|  496:  | 			$preciseNameCallback, | 
|  497:  | 			$preciseNameCallback, | 
|  498:  | 			$preciseWithSubtracted, | 
|  499:  | 			function () use ($preciseWithSubtracted): string { | 
|  500:  | 				$reflection = $this->classReflection; | 
|  501:  | 				$line = ''; | 
|  502:  | 				if ($reflection !== null) { | 
|  503:  | 					$line .= '-'; | 
|  504:  | 					$line .= (string) $reflection->getNativeReflection()->getStartLine(); | 
|  505:  | 					$line .= '-'; | 
|  506:  | 				} | 
|  507:  |  | 
|  508:  | 				return $preciseWithSubtracted() . '-' . static::class . '-' . $line . $this->describeAdditionalCacheKey(); | 
|  509:  | 			}, | 
|  510:  | 		); | 
|  511:  | 	} | 
|  512:  |  | 
|  513:  | 	protected function describeAdditionalCacheKey(): string | 
|  514:  | 	{ | 
|  515:  | 		return ''; | 
|  516:  | 	} | 
|  517:  |  | 
|  518:  | 	private function describeCache(): string | 
|  519:  | 	{ | 
|  520:  | 		if ($this->cachedDescription !== null) { | 
|  521:  | 			return $this->cachedDescription; | 
|  522:  | 		} | 
|  523:  |  | 
|  524:  | 		if (static::class !== self::class) { | 
|  525:  | 			return $this->cachedDescription = $this->describe(VerbosityLevel::cache()); | 
|  526:  | 		} | 
|  527:  |  | 
|  528:  | 		$description = $this->className; | 
|  529:  |  | 
|  530:  | 		if ($this instanceof GenericObjectType) { | 
|  531:  | 			$description .= '<'; | 
|  532:  | 			$typeDescriptions = []; | 
|  533:  | 			foreach ($this->getTypes() as $type) { | 
|  534:  | 				$typeDescriptions[] = $type->describe(VerbosityLevel::cache()); | 
|  535:  | 			} | 
|  536:  | 			$description .= '<' . implode(', ', $typeDescriptions) . '>'; | 
|  537:  | 		} | 
|  538:  |  | 
|  539:  | 		if ($this->subtractedType !== null) { | 
|  540:  | 			$description .= $this->subtractedType instanceof UnionType | 
|  541:  | 				? sprintf('~(%s)', $this->subtractedType->describe(VerbosityLevel::cache())) | 
|  542:  | 				: sprintf('~%s', $this->subtractedType->describe(VerbosityLevel::cache())); | 
|  543:  | 		} | 
|  544:  |  | 
|  545:  | 		$reflection = $this->classReflection; | 
|  546:  | 		if ($reflection !== null) { | 
|  547:  | 			$description .= '-'; | 
|  548:  | 			$description .= (string) $reflection->getNativeReflection()->getStartLine(); | 
|  549:  | 			$description .= '-'; | 
|  550:  | 		} | 
|  551:  |  | 
|  552:  | 		return $this->cachedDescription = $description; | 
|  553:  | 	} | 
|  554:  |  | 
|  555:  | 	public function toNumber(): Type | 
|  556:  | 	{ | 
|  557:  | 		if ($this->isInstanceOf('SimpleXMLElement')->yes()) { | 
|  558:  | 			return new UnionType([ | 
|  559:  | 				new FloatType(), | 
|  560:  | 				new IntegerType(), | 
|  561:  | 			]); | 
|  562:  | 		} | 
|  563:  |  | 
|  564:  | 		return new ErrorType(); | 
|  565:  | 	} | 
|  566:  |  | 
|  567:  | 	public function toAbsoluteNumber(): Type | 
|  568:  | 	{ | 
|  569:  | 		return $this->toNumber()->toAbsoluteNumber(); | 
|  570:  | 	} | 
|  571:  |  | 
|  572:  | 	public function toInteger(): Type | 
|  573:  | 	{ | 
|  574:  | 		if ($this->isInstanceOf('SimpleXMLElement')->yes()) { | 
|  575:  | 			return new IntegerType(); | 
|  576:  | 		} | 
|  577:  |  | 
|  578:  | 		if (in_array($this->getClassName(), ['CurlHandle', 'CurlMultiHandle'], true)) { | 
|  579:  | 			return new IntegerType(); | 
|  580:  | 		} | 
|  581:  |  | 
|  582:  | 		return new ErrorType(); | 
|  583:  | 	} | 
|  584:  |  | 
|  585:  | 	public function toFloat(): Type | 
|  586:  | 	{ | 
|  587:  | 		if ($this->isInstanceOf('SimpleXMLElement')->yes()) { | 
|  588:  | 			return new FloatType(); | 
|  589:  | 		} | 
|  590:  | 		return new ErrorType(); | 
|  591:  | 	} | 
|  592:  |  | 
|  593:  | 	public function toString(): Type | 
|  594:  | 	{ | 
|  595:  | 		$classReflection = $this->getClassReflection(); | 
|  596:  | 		if ($classReflection === null) { | 
|  597:  | 			return new ErrorType(); | 
|  598:  | 		} | 
|  599:  |  | 
|  600:  | 		if ($classReflection->hasNativeMethod('__toString')) { | 
|  601:  | 			return $this->getMethod('__toString', new OutOfClassScope())->getOnlyVariant()->getReturnType(); | 
|  602:  | 		} | 
|  603:  |  | 
|  604:  | 		return new ErrorType(); | 
|  605:  | 	} | 
|  606:  |  | 
|  607:  | 	public function toArray(): Type | 
|  608:  | 	{ | 
|  609:  | 		$classReflection = $this->getClassReflection(); | 
|  610:  | 		if ($classReflection === null) { | 
|  611:  | 			return new ArrayType(new MixedType(), new MixedType()); | 
|  612:  | 		} | 
|  613:  |  | 
|  614:  | 		$reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); | 
|  615:  |  | 
|  616:  | 		if ( | 
|  617:  | 			!$classReflection->getNativeReflection()->isUserDefined() | 
|  618:  | 			|| UniversalObjectCratesClassReflectionExtension::isUniversalObjectCrate( | 
|  619:  | 				$reflectionProvider, | 
|  620:  | 				$classReflection, | 
|  621:  | 			) | 
|  622:  | 		) { | 
|  623:  | 			return new ArrayType(new MixedType(), new MixedType()); | 
|  624:  | 		} | 
|  625:  | 		$arrayKeys = []; | 
|  626:  | 		$arrayValues = []; | 
|  627:  |  | 
|  628:  | 		$isFinal = $classReflection->isFinal(); | 
|  629:  |  | 
|  630:  | 		do { | 
|  631:  | 			foreach ($classReflection->getNativeReflection()->getProperties() as $nativeProperty) { | 
|  632:  | 				if ($nativeProperty->isStatic()) { | 
|  633:  | 					continue; | 
|  634:  | 				} | 
|  635:  |  | 
|  636:  | 				$declaringClass = $reflectionProvider->getClass($nativeProperty->getDeclaringClass()->getName()); | 
|  637:  | 				$property = $declaringClass->getNativeProperty($nativeProperty->getName()); | 
|  638:  |  | 
|  639:  | 				$keyName = $nativeProperty->getName(); | 
|  640:  | 				if ($nativeProperty->isPrivate()) { | 
|  641:  | 					$keyName = sprintf( | 
|  642:  | 						"\0%s\0%s", | 
|  643:  | 						$declaringClass->getName(), | 
|  644:  | 						$keyName, | 
|  645:  | 					); | 
|  646:  | 				} elseif ($nativeProperty->isProtected()) { | 
|  647:  | 					$keyName = sprintf( | 
|  648:  | 						"\0*\0%s", | 
|  649:  | 						$keyName, | 
|  650:  | 					); | 
|  651:  | 				} | 
|  652:  |  | 
|  653:  | 				$arrayKeys[] = new ConstantStringType($keyName); | 
|  654:  | 				$arrayValues[] = $property->getReadableType(); | 
|  655:  | 			} | 
|  656:  |  | 
|  657:  | 			$classReflection = $classReflection->getParentClass(); | 
|  658:  | 		} while ($classReflection !== null); | 
|  659:  |  | 
|  660:  | 		if (!$isFinal && count($arrayKeys) === 0) { | 
|  661:  | 			return new ArrayType(new MixedType(), new MixedType()); | 
|  662:  | 		} | 
|  663:  |  | 
|  664:  | 		return new ConstantArrayType($arrayKeys, $arrayValues); | 
|  665:  | 	} | 
|  666:  |  | 
|  667:  | 	public function toArrayKey(): Type | 
|  668:  | 	{ | 
|  669:  | 		return $this->toString(); | 
|  670:  | 	} | 
|  671:  |  | 
|  672:  | 	public function toBoolean(): BooleanType | 
|  673:  | 	{ | 
|  674:  | 		if ($this->isInstanceOf('SimpleXMLElement')->yes()) { | 
|  675:  | 			return new BooleanType(); | 
|  676:  | 		} | 
|  677:  |  | 
|  678:  | 		return new ConstantBooleanType(true); | 
|  679:  | 	} | 
|  680:  |  | 
|  681:  | 	public function isObject(): TrinaryLogic | 
|  682:  | 	{ | 
|  683:  | 		return TrinaryLogic::createYes(); | 
|  684:  | 	} | 
|  685:  |  | 
|  686:  | 	public function isEnum(): TrinaryLogic | 
|  687:  | 	{ | 
|  688:  | 		$classReflection = $this->getClassReflection(); | 
|  689:  | 		if ($classReflection === null) { | 
|  690:  | 			return TrinaryLogic::createMaybe(); | 
|  691:  | 		} | 
|  692:  |  | 
|  693:  | 		return TrinaryLogic::createFromBoolean($classReflection->isEnum()); | 
|  694:  | 	} | 
|  695:  |  | 
|  696:  | 	public function canAccessProperties(): TrinaryLogic | 
|  697:  | 	{ | 
|  698:  | 		return TrinaryLogic::createYes(); | 
|  699:  | 	} | 
|  700:  |  | 
|  701:  | 	public function canCallMethods(): TrinaryLogic | 
|  702:  | 	{ | 
|  703:  | 		if (strtolower($this->className) === 'stdclass') { | 
|  704:  | 			return TrinaryLogic::createNo(); | 
|  705:  | 		} | 
|  706:  |  | 
|  707:  | 		return TrinaryLogic::createYes(); | 
|  708:  | 	} | 
|  709:  |  | 
|  710:  | 	public function hasMethod(string $methodName): TrinaryLogic | 
|  711:  | 	{ | 
|  712:  | 		$classReflection = $this->getClassReflection(); | 
|  713:  | 		if ($classReflection === null) { | 
|  714:  | 			return TrinaryLogic::createMaybe(); | 
|  715:  | 		} | 
|  716:  |  | 
|  717:  | 		if ($classReflection->hasMethod($methodName)) { | 
|  718:  | 			return TrinaryLogic::createYes(); | 
|  719:  | 		} | 
|  720:  |  | 
|  721:  | 		if ($classReflection->isFinal()) { | 
|  722:  | 			return TrinaryLogic::createNo(); | 
|  723:  | 		} | 
|  724:  |  | 
|  725:  | 		return TrinaryLogic::createMaybe(); | 
|  726:  | 	} | 
|  727:  |  | 
|  728:  | 	public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): ExtendedMethodReflection | 
|  729:  | 	{ | 
|  730:  | 		return $this->getUnresolvedMethodPrototype($methodName, $scope)->getTransformedMethod(); | 
|  731:  | 	} | 
|  732:  |  | 
|  733:  | 	public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection | 
|  734:  | 	{ | 
|  735:  | 		if (!$scope->isInClass()) { | 
|  736:  | 			$canCallMethod = 'no'; | 
|  737:  | 		} else { | 
|  738:  | 			$canCallMethod = $scope->getClassReflection()->getName(); | 
|  739:  | 		} | 
|  740:  | 		$description = $this->describeCache(); | 
|  741:  | 		if (isset(self::$methods[$description][$methodName][$canCallMethod])) { | 
|  742:  | 			return self::$methods[$description][$methodName][$canCallMethod]; | 
|  743:  | 		} | 
|  744:  |  | 
|  745:  | 		$nakedClassReflection = $this->getNakedClassReflection(); | 
|  746:  | 		if ($nakedClassReflection === null) { | 
|  747:  | 			throw new ClassNotFoundException($this->className); | 
|  748:  | 		} | 
|  749:  |  | 
|  750:  | 		if (!$nakedClassReflection->hasNativeMethod($methodName)) { | 
|  751:  | 			$nakedClassReflection = $this->getClassReflection(); | 
|  752:  | 		} | 
|  753:  |  | 
|  754:  | 		if ($nakedClassReflection === null) { | 
|  755:  | 			throw new ClassNotFoundException($this->className); | 
|  756:  | 		} | 
|  757:  |  | 
|  758:  | 		$method = $nakedClassReflection->getMethod($methodName, $scope); | 
|  759:  |  | 
|  760:  | 		$ancestor = $this->getAncestorWithClassName($method->getDeclaringClass()->getName()); | 
|  761:  | 		$resolvedClassReflection = null; | 
|  762:  | 		if ($ancestor !== null) { | 
|  763:  | 			$resolvedClassReflection = $ancestor->getClassReflection(); | 
|  764:  | 			if ($ancestor !== $this) { | 
|  765:  | 				$method = $ancestor->getUnresolvedMethodPrototype($methodName, $scope)->getNakedMethod(); | 
|  766:  | 			} | 
|  767:  | 		} | 
|  768:  | 		if ($resolvedClassReflection === null) { | 
|  769:  | 			$resolvedClassReflection = $method->getDeclaringClass(); | 
|  770:  | 		} | 
|  771:  |  | 
|  772:  | 		return self::$methods[$description][$methodName][$canCallMethod] = new CalledOnTypeUnresolvedMethodPrototypeReflection( | 
|  773:  | 			$method, | 
|  774:  | 			$resolvedClassReflection, | 
|  775:  | 			true, | 
|  776:  | 			$this, | 
|  777:  | 		); | 
|  778:  | 	} | 
|  779:  |  | 
|  780:  | 	public function canAccessConstants(): TrinaryLogic | 
|  781:  | 	{ | 
|  782:  | 		return TrinaryLogic::createYes(); | 
|  783:  | 	} | 
|  784:  |  | 
|  785:  | 	public function hasConstant(string $constantName): TrinaryLogic | 
|  786:  | 	{ | 
|  787:  | 		$class = $this->getClassReflection(); | 
|  788:  | 		if ($class === null) { | 
|  789:  | 			return TrinaryLogic::createNo(); | 
|  790:  | 		} | 
|  791:  |  | 
|  792:  | 		return TrinaryLogic::createFromBoolean( | 
|  793:  | 			$class->hasConstant($constantName), | 
|  794:  | 		); | 
|  795:  | 	} | 
|  796:  |  | 
|  797:  | 	public function getConstant(string $constantName): ClassConstantReflection | 
|  798:  | 	{ | 
|  799:  | 		$class = $this->getClassReflection(); | 
|  800:  | 		if ($class === null) { | 
|  801:  | 			throw new ClassNotFoundException($this->className); | 
|  802:  | 		} | 
|  803:  |  | 
|  804:  | 		return $class->getConstant($constantName); | 
|  805:  | 	} | 
|  806:  |  | 
|  807:  | 	public function getTemplateType(string $ancestorClassName, string $templateTypeName): Type | 
|  808:  | 	{ | 
|  809:  | 		$classReflection = $this->getClassReflection(); | 
|  810:  | 		if ($classReflection === null) { | 
|  811:  | 			return new ErrorType(); | 
|  812:  | 		} | 
|  813:  |  | 
|  814:  | 		$ancestorClassReflection = $classReflection->getAncestorWithClassName($ancestorClassName); | 
|  815:  | 		if ($ancestorClassReflection === null) { | 
|  816:  | 			return new ErrorType(); | 
|  817:  | 		} | 
|  818:  |  | 
|  819:  | 		$activeTemplateTypeMap = $ancestorClassReflection->getPossiblyIncompleteActiveTemplateTypeMap(); | 
|  820:  | 		$type = $activeTemplateTypeMap->getType($templateTypeName); | 
|  821:  | 		if ($type === null) { | 
|  822:  | 			return new ErrorType(); | 
|  823:  | 		} | 
|  824:  | 		if ($type instanceof ErrorType) { | 
|  825:  | 			$templateTypeMap = $ancestorClassReflection->getTemplateTypeMap(); | 
|  826:  | 			$templateType = $templateTypeMap->getType($templateTypeName); | 
|  827:  | 			if ($templateType === null) { | 
|  828:  | 				return $type; | 
|  829:  | 			} | 
|  830:  |  | 
|  831:  | 			$bound = TemplateTypeHelper::resolveToBounds($templateType); | 
|  832:  | 			if ($bound instanceof MixedType && $bound->isExplicitMixed()) { | 
|  833:  | 				return new MixedType(false); | 
|  834:  | 			} | 
|  835:  |  | 
|  836:  | 			return TemplateTypeHelper::resolveToDefaults($templateType); | 
|  837:  | 		} | 
|  838:  |  | 
|  839:  | 		return $type; | 
|  840:  | 	} | 
|  841:  |  | 
|  842:  | 	public function getConstantStrings(): array | 
|  843:  | 	{ | 
|  844:  | 		return []; | 
|  845:  | 	} | 
|  846:  |  | 
|  847:  | 	public function isIterable(): TrinaryLogic | 
|  848:  | 	{ | 
|  849:  | 		return $this->isInstanceOf(Traversable::class); | 
|  850:  | 	} | 
|  851:  |  | 
|  852:  | 	public function isIterableAtLeastOnce(): TrinaryLogic | 
|  853:  | 	{ | 
|  854:  | 		return $this->isInstanceOf(Traversable::class) | 
|  855:  | 			->and(TrinaryLogic::createMaybe()); | 
|  856:  | 	} | 
|  857:  |  | 
|  858:  | 	public function getArraySize(): Type | 
|  859:  | 	{ | 
|  860:  | 		if ($this->isInstanceOf(Countable::class)->no()) { | 
|  861:  | 			return new ErrorType(); | 
|  862:  | 		} | 
|  863:  |  | 
|  864:  | 		return IntegerRangeType::fromInterval(0, null); | 
|  865:  | 	} | 
|  866:  |  | 
|  867:  | 	public function getIterableKeyType(): Type | 
|  868:  | 	{ | 
|  869:  | 		$isTraversable = false; | 
|  870:  | 		if ($this->isInstanceOf(IteratorAggregate::class)->yes()) { | 
|  871:  | 			$keyType = RecursionGuard::run($this, fn (): Type => $this->getMethod('getIterator', new OutOfClassScope())->getOnlyVariant()->getReturnType()->getIterableKeyType()); | 
|  872:  | 			$isTraversable = true; | 
|  873:  | 			if (!$keyType instanceof MixedType || $keyType->isExplicitMixed()) { | 
|  874:  | 				return $keyType; | 
|  875:  | 			} | 
|  876:  | 		} | 
|  877:  |  | 
|  878:  | 		$extraOffsetAccessible = $this->isExtraOffsetAccessibleClass()->yes(); | 
|  879:  | 		if (!$extraOffsetAccessible && $this->isInstanceOf(Traversable::class)->yes()) { | 
|  880:  | 			$isTraversable = true; | 
|  881:  | 			$tKey = $this->getTemplateType(Traversable::class, 'TKey'); | 
|  882:  | 			if (!$tKey instanceof ErrorType) { | 
|  883:  | 				if (!$tKey instanceof MixedType || $tKey->isExplicitMixed()) { | 
|  884:  | 					return $tKey; | 
|  885:  | 				} | 
|  886:  | 			} | 
|  887:  | 		} | 
|  888:  |  | 
|  889:  | 		if ($this->isInstanceOf(Iterator::class)->yes()) { | 
|  890:  | 			return RecursionGuard::run($this, fn (): Type => $this->getMethod('key', new OutOfClassScope())->getOnlyVariant()->getReturnType()); | 
|  891:  | 		} | 
|  892:  |  | 
|  893:  | 		if ($extraOffsetAccessible) { | 
|  894:  | 			return new MixedType(true); | 
|  895:  | 		} | 
|  896:  |  | 
|  897:  | 		if ($isTraversable) { | 
|  898:  | 			return new MixedType(); | 
|  899:  | 		} | 
|  900:  |  | 
|  901:  | 		return new ErrorType(); | 
|  902:  | 	} | 
|  903:  |  | 
|  904:  | 	public function getFirstIterableKeyType(): Type | 
|  905:  | 	{ | 
|  906:  | 		return $this->getIterableKeyType(); | 
|  907:  | 	} | 
|  908:  |  | 
|  909:  | 	public function getLastIterableKeyType(): Type | 
|  910:  | 	{ | 
|  911:  | 		return $this->getIterableKeyType(); | 
|  912:  | 	} | 
|  913:  |  | 
|  914:  | 	public function getIterableValueType(): Type | 
|  915:  | 	{ | 
|  916:  | 		$isTraversable = false; | 
|  917:  | 		if ($this->isInstanceOf(IteratorAggregate::class)->yes()) { | 
|  918:  | 			$valueType = RecursionGuard::run($this, fn (): Type => $this->getMethod('getIterator', new OutOfClassScope())->getOnlyVariant()->getReturnType()->getIterableValueType()); | 
|  919:  | 			$isTraversable = true; | 
|  920:  | 			if (!$valueType instanceof MixedType || $valueType->isExplicitMixed()) { | 
|  921:  | 				return $valueType; | 
|  922:  | 			} | 
|  923:  | 		} | 
|  924:  |  | 
|  925:  | 		$extraOffsetAccessible = $this->isExtraOffsetAccessibleClass()->yes(); | 
|  926:  | 		if (!$extraOffsetAccessible && $this->isInstanceOf(Traversable::class)->yes()) { | 
|  927:  | 			$isTraversable = true; | 
|  928:  | 			$tValue = $this->getTemplateType(Traversable::class, 'TValue'); | 
|  929:  | 			if (!$tValue instanceof ErrorType) { | 
|  930:  | 				if (!$tValue instanceof MixedType || $tValue->isExplicitMixed()) { | 
|  931:  | 					return $tValue; | 
|  932:  | 				} | 
|  933:  | 			} | 
|  934:  | 		} | 
|  935:  |  | 
|  936:  | 		if ($this->isInstanceOf(Iterator::class)->yes()) { | 
|  937:  | 			return RecursionGuard::run($this, fn (): Type => $this->getMethod('current', new OutOfClassScope())->getOnlyVariant()->getReturnType()); | 
|  938:  | 		} | 
|  939:  |  | 
|  940:  | 		if ($extraOffsetAccessible) { | 
|  941:  | 			return new MixedType(true); | 
|  942:  | 		} | 
|  943:  |  | 
|  944:  | 		if ($isTraversable) { | 
|  945:  | 			return new MixedType(); | 
|  946:  | 		} | 
|  947:  |  | 
|  948:  | 		return new ErrorType(); | 
|  949:  | 	} | 
|  950:  |  | 
|  951:  | 	public function getFirstIterableValueType(): Type | 
|  952:  | 	{ | 
|  953:  | 		return $this->getIterableValueType(); | 
|  954:  | 	} | 
|  955:  |  | 
|  956:  | 	public function getLastIterableValueType(): Type | 
|  957:  | 	{ | 
|  958:  | 		return $this->getIterableValueType(); | 
|  959:  | 	} | 
|  960:  |  | 
|  961:  | 	public function isNull(): TrinaryLogic | 
|  962:  | 	{ | 
|  963:  | 		return TrinaryLogic::createNo(); | 
|  964:  | 	} | 
|  965:  |  | 
|  966:  | 	public function isConstantValue(): TrinaryLogic | 
|  967:  | 	{ | 
|  968:  | 		return TrinaryLogic::createNo(); | 
|  969:  | 	} | 
|  970:  |  | 
|  971:  | 	public function isConstantScalarValue(): TrinaryLogic | 
|  972:  | 	{ | 
|  973:  | 		return TrinaryLogic::createNo(); | 
|  974:  | 	} | 
|  975:  |  | 
|  976:  | 	public function getConstantScalarTypes(): array | 
|  977:  | 	{ | 
|  978:  | 		return []; | 
|  979:  | 	} | 
|  980:  |  | 
|  981:  | 	public function getConstantScalarValues(): array | 
|  982:  | 	{ | 
|  983:  | 		return []; | 
|  984:  | 	} | 
|  985:  |  | 
|  986:  | 	public function isTrue(): TrinaryLogic | 
|  987:  | 	{ | 
|  988:  | 		return TrinaryLogic::createNo(); | 
|  989:  | 	} | 
|  990:  |  | 
|  991:  | 	public function isFalse(): TrinaryLogic | 
|  992:  | 	{ | 
|  993:  | 		return TrinaryLogic::createNo(); | 
|  994:  | 	} | 
|  995:  |  | 
|  996:  | 	public function isBoolean(): TrinaryLogic | 
|  997:  | 	{ | 
|  998:  | 		return TrinaryLogic::createNo(); | 
|  999:  | 	} | 
| 1000:  |  | 
| 1001:  | 	public function isFloat(): TrinaryLogic | 
| 1002:  | 	{ | 
| 1003:  | 		return TrinaryLogic::createNo(); | 
| 1004:  | 	} | 
| 1005:  |  | 
| 1006:  | 	public function isInteger(): TrinaryLogic | 
| 1007:  | 	{ | 
| 1008:  | 		return TrinaryLogic::createNo(); | 
| 1009:  | 	} | 
| 1010:  |  | 
| 1011:  | 	public function isString(): TrinaryLogic | 
| 1012:  | 	{ | 
| 1013:  | 		return TrinaryLogic::createNo(); | 
| 1014:  | 	} | 
| 1015:  |  | 
| 1016:  | 	public function isNumericString(): TrinaryLogic | 
| 1017:  | 	{ | 
| 1018:  | 		return TrinaryLogic::createNo(); | 
| 1019:  | 	} | 
| 1020:  |  | 
| 1021:  | 	public function isNonEmptyString(): TrinaryLogic | 
| 1022:  | 	{ | 
| 1023:  | 		return TrinaryLogic::createNo(); | 
| 1024:  | 	} | 
| 1025:  |  | 
| 1026:  | 	public function isNonFalsyString(): TrinaryLogic | 
| 1027:  | 	{ | 
| 1028:  | 		return TrinaryLogic::createNo(); | 
| 1029:  | 	} | 
| 1030:  |  | 
| 1031:  | 	public function isLiteralString(): TrinaryLogic | 
| 1032:  | 	{ | 
| 1033:  | 		return TrinaryLogic::createNo(); | 
| 1034:  | 	} | 
| 1035:  |  | 
| 1036:  | 	public function isLowercaseString(): TrinaryLogic | 
| 1037:  | 	{ | 
| 1038:  | 		return TrinaryLogic::createNo(); | 
| 1039:  | 	} | 
| 1040:  |  | 
| 1041:  | 	public function isClassString(): TrinaryLogic | 
| 1042:  | 	{ | 
| 1043:  | 		return TrinaryLogic::createNo(); | 
| 1044:  | 	} | 
| 1045:  |  | 
| 1046:  | 	public function isUppercaseString(): TrinaryLogic | 
| 1047:  | 	{ | 
| 1048:  | 		return TrinaryLogic::createNo(); | 
| 1049:  | 	} | 
| 1050:  |  | 
| 1051:  | 	public function getClassStringObjectType(): Type | 
| 1052:  | 	{ | 
| 1053:  | 		return new ErrorType(); | 
| 1054:  | 	} | 
| 1055:  |  | 
| 1056:  | 	public function getObjectTypeOrClassStringObjectType(): Type | 
| 1057:  | 	{ | 
| 1058:  | 		return $this; | 
| 1059:  | 	} | 
| 1060:  |  | 
| 1061:  | 	public function isVoid(): TrinaryLogic | 
| 1062:  | 	{ | 
| 1063:  | 		return TrinaryLogic::createNo(); | 
| 1064:  | 	} | 
| 1065:  |  | 
| 1066:  | 	public function isScalar(): TrinaryLogic | 
| 1067:  | 	{ | 
| 1068:  | 		return TrinaryLogic::createNo(); | 
| 1069:  | 	} | 
| 1070:  |  | 
| 1071:  | 	public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType | 
| 1072:  | 	{ | 
| 1073:  | 		if ($type->isTrue()->yes()) { | 
| 1074:  | 			return new ConstantBooleanType(true); | 
| 1075:  | 		} | 
| 1076:  |  | 
| 1077:  | 		return $type->isFalse()->yes() | 
| 1078:  | 			? new ConstantBooleanType(false) | 
| 1079:  | 			: new BooleanType(); | 
| 1080:  | 	} | 
| 1081:  |  | 
| 1082:  | 	private function isExtraOffsetAccessibleClass(): TrinaryLogic | 
| 1083:  | 	{ | 
| 1084:  | 		$classReflection = $this->getClassReflection(); | 
| 1085:  | 		if ($classReflection === null) { | 
| 1086:  | 			return TrinaryLogic::createMaybe(); | 
| 1087:  | 		} | 
| 1088:  |  | 
| 1089:  | 		foreach (self::EXTRA_OFFSET_CLASSES as $extraOffsetClass) { | 
| 1090:  | 			if ($classReflection->getName() === $extraOffsetClass) { | 
| 1091:  | 				return TrinaryLogic::createYes(); | 
| 1092:  | 			} | 
| 1093:  | 			if ($classReflection->isSubclassOf($extraOffsetClass)) { | 
| 1094:  | 				return TrinaryLogic::createYes(); | 
| 1095:  | 			} | 
| 1096:  | 		} | 
| 1097:  |  | 
| 1098:  | 		if ($classReflection->isInterface()) { | 
| 1099:  | 			return TrinaryLogic::createMaybe(); | 
| 1100:  | 		} | 
| 1101:  |  | 
| 1102:  | 		if ($classReflection->isFinal()) { | 
| 1103:  | 			return TrinaryLogic::createNo(); | 
| 1104:  | 		} | 
| 1105:  |  | 
| 1106:  | 		return TrinaryLogic::createMaybe(); | 
| 1107:  | 	} | 
| 1108:  |  | 
| 1109:  | 	public function isOffsetAccessible(): TrinaryLogic | 
| 1110:  | 	{ | 
| 1111:  | 		return $this->isInstanceOf(ArrayAccess::class)->or( | 
| 1112:  | 			$this->isExtraOffsetAccessibleClass(), | 
| 1113:  | 		); | 
| 1114:  | 	} | 
| 1115:  |  | 
| 1116:  | 	public function isOffsetAccessLegal(): TrinaryLogic | 
| 1117:  | 	{ | 
| 1118:  | 		return $this->isOffsetAccessible(); | 
| 1119:  | 	} | 
| 1120:  |  | 
| 1121:  | 	public function hasOffsetValueType(Type $offsetType): TrinaryLogic | 
| 1122:  | 	{ | 
| 1123:  | 		if ($this->isInstanceOf(ArrayAccess::class)->yes()) { | 
| 1124:  | 			$acceptedOffsetType = RecursionGuard::run($this, function (): Type { | 
| 1125:  | 				$parameters = $this->getMethod('offsetSet', new OutOfClassScope())->getOnlyVariant()->getParameters(); | 
| 1126:  | 				if (count($parameters) < 2) { | 
| 1127:  | 					throw new ShouldNotHappenException(sprintf( | 
| 1128:  | 						'Method %s::%s() has less than 2 parameters.', | 
| 1129:  | 						$this->className, | 
| 1130:  | 						'offsetSet', | 
| 1131:  | 					)); | 
| 1132:  | 				} | 
| 1133:  |  | 
| 1134:  | 				$offsetParameter = $parameters[0]; | 
| 1135:  |  | 
| 1136:  | 				return $offsetParameter->getType(); | 
| 1137:  | 			}); | 
| 1138:  |  | 
| 1139:  | 			if ($acceptedOffsetType->isSuperTypeOf($offsetType)->no()) { | 
| 1140:  | 				return TrinaryLogic::createNo(); | 
| 1141:  | 			} | 
| 1142:  |  | 
| 1143:  | 			return TrinaryLogic::createMaybe(); | 
| 1144:  | 		} | 
| 1145:  |  | 
| 1146:  | 		return $this->isExtraOffsetAccessibleClass() | 
| 1147:  | 			->and(TrinaryLogic::createMaybe()); | 
| 1148:  | 	} | 
| 1149:  |  | 
| 1150:  | 	public function getOffsetValueType(Type $offsetType): Type | 
| 1151:  | 	{ | 
| 1152:  | 		if (!$this->isExtraOffsetAccessibleClass()->no()) { | 
| 1153:  | 			return new MixedType(); | 
| 1154:  | 		} | 
| 1155:  |  | 
| 1156:  | 		if ($this->isInstanceOf(ArrayAccess::class)->yes()) { | 
| 1157:  | 			return RecursionGuard::run($this, fn (): Type => $this->getMethod('offsetGet', new OutOfClassScope())->getOnlyVariant()->getReturnType()); | 
| 1158:  | 		} | 
| 1159:  |  | 
| 1160:  | 		return new ErrorType(); | 
| 1161:  | 	} | 
| 1162:  |  | 
| 1163:  | 	public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type | 
| 1164:  | 	{ | 
| 1165:  | 		if ($this->isOffsetAccessible()->no()) { | 
| 1166:  | 			return new ErrorType(); | 
| 1167:  | 		} | 
| 1168:  |  | 
| 1169:  | 		if ($this->isInstanceOf(ArrayAccess::class)->yes()) { | 
| 1170:  | 			$acceptedValueType = new NeverType(); | 
| 1171:  | 			$acceptedOffsetType = RecursionGuard::run($this, function () use (&$acceptedValueType): Type { | 
| 1172:  | 				$parameters = $this->getMethod('offsetSet', new OutOfClassScope())->getOnlyVariant()->getParameters(); | 
| 1173:  | 				if (count($parameters) < 2) { | 
| 1174:  | 					throw new ShouldNotHappenException(sprintf( | 
| 1175:  | 						'Method %s::%s() has less than 2 parameters.', | 
| 1176:  | 						$this->className, | 
| 1177:  | 						'offsetSet', | 
| 1178:  | 					)); | 
| 1179:  | 				} | 
| 1180:  |  | 
| 1181:  | 				$offsetParameter = $parameters[0]; | 
| 1182:  | 				$acceptedValueType = $parameters[1]->getType(); | 
| 1183:  |  | 
| 1184:  | 				return $offsetParameter->getType(); | 
| 1185:  | 			}); | 
| 1186:  |  | 
| 1187:  | 			if ($offsetType === null) { | 
| 1188:  | 				$offsetType = new NullType(); | 
| 1189:  | 			} | 
| 1190:  |  | 
| 1191:  | 			if ( | 
| 1192:  | 				(!$offsetType instanceof MixedType && !$acceptedOffsetType->isSuperTypeOf($offsetType)->yes()) | 
| 1193:  | 				|| (!$valueType instanceof MixedType && !$acceptedValueType->isSuperTypeOf($valueType)->yes()) | 
| 1194:  | 			) { | 
| 1195:  | 				return new ErrorType(); | 
| 1196:  | 			} | 
| 1197:  | 		} | 
| 1198:  |  | 
| 1199:  | 		 | 
| 1200:  | 		return $this; | 
| 1201:  | 	} | 
| 1202:  |  | 
| 1203:  | 	public function setExistingOffsetValueType(Type $offsetType, Type $valueType): Type | 
| 1204:  | 	{ | 
| 1205:  | 		if ($this->isOffsetAccessible()->no()) { | 
| 1206:  | 			return new ErrorType(); | 
| 1207:  | 		} | 
| 1208:  |  | 
| 1209:  | 		return $this; | 
| 1210:  | 	} | 
| 1211:  |  | 
| 1212:  | 	public function unsetOffset(Type $offsetType): Type | 
| 1213:  | 	{ | 
| 1214:  | 		if ($this->isOffsetAccessible()->no()) { | 
| 1215:  | 			return new ErrorType(); | 
| 1216:  | 		} | 
| 1217:  |  | 
| 1218:  | 		return $this; | 
| 1219:  | 	} | 
| 1220:  |  | 
| 1221:  | 	public function getEnumCases(): array | 
| 1222:  | 	{ | 
| 1223:  | 		$classReflection = $this->getClassReflection(); | 
| 1224:  | 		if ($classReflection === null) { | 
| 1225:  | 			return []; | 
| 1226:  | 		} | 
| 1227:  |  | 
| 1228:  | 		if (!$classReflection->isEnum()) { | 
| 1229:  | 			return []; | 
| 1230:  | 		} | 
| 1231:  |  | 
| 1232:  | 		$cacheKey = $this->describeCache(); | 
| 1233:  | 		if (array_key_exists($cacheKey, self::$enumCases)) { | 
| 1234:  | 			return self::$enumCases[$cacheKey]; | 
| 1235:  | 		} | 
| 1236:  |  | 
| 1237:  | 		$className = $classReflection->getName(); | 
| 1238:  |  | 
| 1239:  | 		if ($this->subtractedType !== null) { | 
| 1240:  | 			$subtractedEnumCaseNames = []; | 
| 1241:  |  | 
| 1242:  | 			foreach ($this->subtractedType->getEnumCases() as $subtractedCase) { | 
| 1243:  | 				$subtractedEnumCaseNames[$subtractedCase->getEnumCaseName()] = true; | 
| 1244:  | 			} | 
| 1245:  |  | 
| 1246:  | 			$cases = []; | 
| 1247:  | 			foreach ($classReflection->getEnumCases() as $enumCase) { | 
| 1248:  | 				if (array_key_exists($enumCase->getName(), $subtractedEnumCaseNames)) { | 
| 1249:  | 					continue; | 
| 1250:  | 				} | 
| 1251:  | 				$cases[] = new EnumCaseObjectType($className, $enumCase->getName(), $classReflection); | 
| 1252:  | 			} | 
| 1253:  | 		} else { | 
| 1254:  | 			$cases = []; | 
| 1255:  | 			foreach ($classReflection->getEnumCases() as $enumCase) { | 
| 1256:  | 				$cases[] = new EnumCaseObjectType($className, $enumCase->getName(), $classReflection); | 
| 1257:  | 			} | 
| 1258:  | 		} | 
| 1259:  |  | 
| 1260:  | 		return self::$enumCases[$cacheKey] = $cases; | 
| 1261:  | 	} | 
| 1262:  |  | 
| 1263:  | 	public function isCallable(): TrinaryLogic | 
| 1264:  | 	{ | 
| 1265:  | 		$parametersAcceptors = RecursionGuard::run($this, fn () => $this->findCallableParametersAcceptors()); | 
| 1266:  | 		if ($parametersAcceptors === null) { | 
| 1267:  | 			return TrinaryLogic::createNo(); | 
| 1268:  | 		} | 
| 1269:  | 		if ($parametersAcceptors instanceof ErrorType) { | 
| 1270:  | 			return TrinaryLogic::createNo(); | 
| 1271:  | 		} | 
| 1272:  |  | 
| 1273:  | 		if ( | 
| 1274:  | 			count($parametersAcceptors) === 1 | 
| 1275:  | 			&& $parametersAcceptors[0] instanceof TrivialParametersAcceptor | 
| 1276:  | 		) { | 
| 1277:  | 			return TrinaryLogic::createMaybe(); | 
| 1278:  | 		} | 
| 1279:  |  | 
| 1280:  | 		return TrinaryLogic::createYes(); | 
| 1281:  | 	} | 
| 1282:  |  | 
| 1283:  | 	public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array | 
| 1284:  | 	{ | 
| 1285:  | 		if ($this->className === Closure::class) { | 
| 1286:  | 			return [new TrivialParametersAcceptor('Closure')]; | 
| 1287:  | 		} | 
| 1288:  | 		$parametersAcceptors = $this->findCallableParametersAcceptors(); | 
| 1289:  | 		if ($parametersAcceptors === null) { | 
| 1290:  | 			throw new ShouldNotHappenException(); | 
| 1291:  | 		} | 
| 1292:  |  | 
| 1293:  | 		return $parametersAcceptors; | 
| 1294:  | 	} | 
| 1295:  |  | 
| 1296:  | 	 | 
| 1297:  |  | 
| 1298:  |  | 
| 1299:  | 	private function findCallableParametersAcceptors(): ?array | 
| 1300:  | 	{ | 
| 1301:  | 		$classReflection = $this->getClassReflection(); | 
| 1302:  | 		if ($classReflection === null) { | 
| 1303:  | 			return [new TrivialParametersAcceptor()]; | 
| 1304:  | 		} | 
| 1305:  |  | 
| 1306:  | 		if ($classReflection->hasNativeMethod('__invoke')) { | 
| 1307:  | 			$method = $this->getMethod('__invoke', new OutOfClassScope()); | 
| 1308:  | 			return FunctionCallableVariant::createFromVariants( | 
| 1309:  | 				$method, | 
| 1310:  | 				$method->getVariants(), | 
| 1311:  | 			); | 
| 1312:  | 		} | 
| 1313:  |  | 
| 1314:  | 		if (!$classReflection->getNativeReflection()->isFinal()) { | 
| 1315:  | 			return [new TrivialParametersAcceptor()]; | 
| 1316:  | 		} | 
| 1317:  |  | 
| 1318:  | 		return null; | 
| 1319:  | 	} | 
| 1320:  |  | 
| 1321:  | 	public function isCloneable(): TrinaryLogic | 
| 1322:  | 	{ | 
| 1323:  | 		return TrinaryLogic::createYes(); | 
| 1324:  | 	} | 
| 1325:  |  | 
| 1326:  | 	public function isInstanceOf(string $className): TrinaryLogic | 
| 1327:  | 	{ | 
| 1328:  | 		$classReflection = $this->getClassReflection(); | 
| 1329:  | 		if ($classReflection === null) { | 
| 1330:  | 			return TrinaryLogic::createMaybe(); | 
| 1331:  | 		} | 
| 1332:  |  | 
| 1333:  | 		if ($classReflection->getName() === $className || $classReflection->isSubclassOf($className)) { | 
| 1334:  | 			return TrinaryLogic::createYes(); | 
| 1335:  | 		} | 
| 1336:  |  | 
| 1337:  | 		$reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); | 
| 1338:  | 		if ($reflectionProvider->hasClass($className)) { | 
| 1339:  | 			$thatClassReflection = $reflectionProvider->getClass($className); | 
| 1340:  | 			if ($thatClassReflection->isFinal()) { | 
| 1341:  | 				return TrinaryLogic::createNo(); | 
| 1342:  | 			} | 
| 1343:  | 		} | 
| 1344:  |  | 
| 1345:  | 		if ($classReflection->isInterface()) { | 
| 1346:  | 			return TrinaryLogic::createMaybe(); | 
| 1347:  | 		} | 
| 1348:  |  | 
| 1349:  | 		return TrinaryLogic::createNo(); | 
| 1350:  | 	} | 
| 1351:  |  | 
| 1352:  | 	public function subtract(Type $type): Type | 
| 1353:  | 	{ | 
| 1354:  | 		if ($this->subtractedType !== null) { | 
| 1355:  | 			$type = TypeCombinator::union($this->subtractedType, $type); | 
| 1356:  | 		} | 
| 1357:  |  | 
| 1358:  | 		return $this->changeSubtractedType($type); | 
| 1359:  | 	} | 
| 1360:  |  | 
| 1361:  | 	public function getTypeWithoutSubtractedType(): Type | 
| 1362:  | 	{ | 
| 1363:  | 		return $this->changeSubtractedType(null); | 
| 1364:  | 	} | 
| 1365:  |  | 
| 1366:  | 	public function changeSubtractedType(?Type $subtractedType): Type | 
| 1367:  | 	{ | 
| 1368:  | 		if ($subtractedType !== null) { | 
| 1369:  | 			$classReflection = $this->getClassReflection(); | 
| 1370:  | 			$allowedSubTypes = $classReflection !== null ? $classReflection->getAllowedSubTypes() : null; | 
| 1371:  | 			if ($allowedSubTypes !== null) { | 
| 1372:  | 				$preciseVerbosity = VerbosityLevel::precise(); | 
| 1373:  |  | 
| 1374:  | 				$originalAllowedSubTypes = $allowedSubTypes; | 
| 1375:  | 				$subtractedSubTypes = []; | 
| 1376:  |  | 
| 1377:  | 				$subtractedTypes = TypeUtils::flattenTypes($subtractedType); | 
| 1378:  | 				foreach ($subtractedTypes as $subType) { | 
| 1379:  | 					foreach ($allowedSubTypes as $key => $allowedSubType) { | 
| 1380:  | 						if ($subType->equals($allowedSubType)) { | 
| 1381:  | 							$description = $allowedSubType->describe($preciseVerbosity); | 
| 1382:  | 							$subtractedSubTypes[$description] = $subType; | 
| 1383:  | 							unset($allowedSubTypes[$key]); | 
| 1384:  | 							continue 2; | 
| 1385:  | 						} | 
| 1386:  | 					} | 
| 1387:  |  | 
| 1388:  | 					return new self($this->className, $subtractedType); | 
| 1389:  | 				} | 
| 1390:  |  | 
| 1391:  | 				if (count($allowedSubTypes) === 1) { | 
| 1392:  | 					return array_values($allowedSubTypes)[0]; | 
| 1393:  | 				} | 
| 1394:  |  | 
| 1395:  | 				$subtractedSubTypes = array_values($subtractedSubTypes); | 
| 1396:  | 				$subtractedSubTypesCount = count($subtractedSubTypes); | 
| 1397:  | 				if ($subtractedSubTypesCount === count($originalAllowedSubTypes)) { | 
| 1398:  | 					return new NeverType(); | 
| 1399:  | 				} | 
| 1400:  |  | 
| 1401:  | 				if ($subtractedSubTypesCount === 0) { | 
| 1402:  | 					return new self($this->className); | 
| 1403:  | 				} | 
| 1404:  |  | 
| 1405:  | 				if ($subtractedSubTypesCount === 1) { | 
| 1406:  | 					return new self($this->className, $subtractedSubTypes[0]); | 
| 1407:  | 				} | 
| 1408:  |  | 
| 1409:  | 				return new self($this->className, new UnionType($subtractedSubTypes)); | 
| 1410:  | 			} | 
| 1411:  | 		} | 
| 1412:  |  | 
| 1413:  | 		if ($this->subtractedType === null && $subtractedType === null) { | 
| 1414:  | 			return $this; | 
| 1415:  | 		} | 
| 1416:  |  | 
| 1417:  | 		return new self($this->className, $subtractedType); | 
| 1418:  | 	} | 
| 1419:  |  | 
| 1420:  | 	public function getSubtractedType(): ?Type | 
| 1421:  | 	{ | 
| 1422:  | 		return $this->subtractedType; | 
| 1423:  | 	} | 
| 1424:  |  | 
| 1425:  | 	public function traverse(callable $cb): Type | 
| 1426:  | 	{ | 
| 1427:  | 		$subtractedType = $this->subtractedType !== null ? $cb($this->subtractedType) : null; | 
| 1428:  |  | 
| 1429:  | 		if ($subtractedType !== $this->subtractedType) { | 
| 1430:  | 			return new self( | 
| 1431:  | 				$this->className, | 
| 1432:  | 				$subtractedType, | 
| 1433:  | 			); | 
| 1434:  | 		} | 
| 1435:  |  | 
| 1436:  | 		return $this; | 
| 1437:  | 	} | 
| 1438:  |  | 
| 1439:  | 	public function traverseSimultaneously(Type $right, callable $cb): Type | 
| 1440:  | 	{ | 
| 1441:  | 		if ($this->subtractedType === null) { | 
| 1442:  | 			return $this; | 
| 1443:  | 		} | 
| 1444:  |  | 
| 1445:  | 		return new self($this->className); | 
| 1446:  | 	} | 
| 1447:  |  | 
| 1448:  | 	public function getNakedClassReflection(): ?ClassReflection | 
| 1449:  | 	{ | 
| 1450:  | 		if ($this->classReflection !== null) { | 
| 1451:  | 			return $this->classReflection; | 
| 1452:  | 		} | 
| 1453:  |  | 
| 1454:  | 		$reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); | 
| 1455:  | 		if (!$reflectionProvider->hasClass($this->className)) { | 
| 1456:  | 			return null; | 
| 1457:  | 		} | 
| 1458:  |  | 
| 1459:  | 		return $reflectionProvider->getClass($this->className); | 
| 1460:  | 	} | 
| 1461:  |  | 
| 1462:  | 	public function getClassReflection(): ?ClassReflection | 
| 1463:  | 	{ | 
| 1464:  | 		if ($this->classReflection !== null) { | 
| 1465:  | 			return $this->classReflection; | 
| 1466:  | 		} | 
| 1467:  |  | 
| 1468:  | 		$reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); | 
| 1469:  | 		if (!$reflectionProvider->hasClass($this->className)) { | 
| 1470:  | 			return null; | 
| 1471:  | 		} | 
| 1472:  |  | 
| 1473:  | 		$classReflection = $reflectionProvider->getClass($this->className); | 
| 1474:  | 		if ($classReflection->isGeneric()) { | 
| 1475:  | 			return $classReflection->withTypes(array_values($classReflection->getTemplateTypeMap()->map(static fn (): Type => new ErrorType())->getTypes())); | 
| 1476:  | 		} | 
| 1477:  |  | 
| 1478:  | 		return $classReflection; | 
| 1479:  | 	} | 
| 1480:  |  | 
| 1481:  | 	public function getAncestorWithClassName(string $className): ?self | 
| 1482:  | 	{ | 
| 1483:  | 		if ($this->className === $className) { | 
| 1484:  | 			return $this; | 
| 1485:  | 		} | 
| 1486:  |  | 
| 1487:  | 		if ($this->classReflection !== null && $className === $this->classReflection->getName()) { | 
| 1488:  | 			return $this; | 
| 1489:  | 		} | 
| 1490:  |  | 
| 1491:  | 		if (array_key_exists($className, $this->currentAncestors)) { | 
| 1492:  | 			return $this->currentAncestors[$className]; | 
| 1493:  | 		} | 
| 1494:  |  | 
| 1495:  | 		$description = $this->describeCache(); | 
| 1496:  | 		if ( | 
| 1497:  | 			array_key_exists($description, self::$ancestors) | 
| 1498:  | 			&& array_key_exists($className, self::$ancestors[$description]) | 
| 1499:  | 		) { | 
| 1500:  | 			return self::$ancestors[$description][$className]; | 
| 1501:  | 		} | 
| 1502:  |  | 
| 1503:  | 		$reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); | 
| 1504:  | 		if (!$reflectionProvider->hasClass($className)) { | 
| 1505:  | 			return self::$ancestors[$description][$className] = $this->currentAncestors[$className] = null; | 
| 1506:  | 		} | 
| 1507:  | 		$theirReflection = $reflectionProvider->getClass($className); | 
| 1508:  |  | 
| 1509:  | 		$thisReflection = $this->getClassReflection(); | 
| 1510:  | 		if ($thisReflection === null) { | 
| 1511:  | 			return self::$ancestors[$description][$className] = $this->currentAncestors[$className] = null; | 
| 1512:  | 		} | 
| 1513:  | 		if ($theirReflection->getName() === $thisReflection->getName()) { | 
| 1514:  | 			return self::$ancestors[$description][$className] = $this->currentAncestors[$className] = $this; | 
| 1515:  | 		} | 
| 1516:  |  | 
| 1517:  | 		foreach ($this->getInterfaces() as $interface) { | 
| 1518:  | 			$ancestor = $interface->getAncestorWithClassName($className); | 
| 1519:  | 			if ($ancestor !== null) { | 
| 1520:  | 				return self::$ancestors[$description][$className] = $this->currentAncestors[$className] = $ancestor; | 
| 1521:  | 			} | 
| 1522:  | 		} | 
| 1523:  |  | 
| 1524:  | 		$parent = $this->getParent(); | 
| 1525:  | 		if ($parent !== null) { | 
| 1526:  | 			$ancestor = $parent->getAncestorWithClassName($className); | 
| 1527:  | 			if ($ancestor !== null) { | 
| 1528:  | 				return self::$ancestors[$description][$className] = $this->currentAncestors[$className] = $ancestor; | 
| 1529:  | 			} | 
| 1530:  | 		} | 
| 1531:  |  | 
| 1532:  | 		return self::$ancestors[$description][$className] = $this->currentAncestors[$className] = null; | 
| 1533:  | 	} | 
| 1534:  |  | 
| 1535:  | 	private function getParent(): ?ObjectType | 
| 1536:  | 	{ | 
| 1537:  | 		if ($this->cachedParent !== null) { | 
| 1538:  | 			return $this->cachedParent; | 
| 1539:  | 		} | 
| 1540:  | 		$thisReflection = $this->getClassReflection(); | 
| 1541:  | 		if ($thisReflection === null) { | 
| 1542:  | 			return null; | 
| 1543:  | 		} | 
| 1544:  |  | 
| 1545:  | 		$parentReflection = $thisReflection->getParentClass(); | 
| 1546:  | 		if ($parentReflection === null) { | 
| 1547:  | 			return null; | 
| 1548:  | 		} | 
| 1549:  |  | 
| 1550:  | 		return $this->cachedParent = self::createFromReflection($parentReflection); | 
| 1551:  | 	} | 
| 1552:  |  | 
| 1553:  | 	 | 
| 1554:  | 	private function getInterfaces(): array | 
| 1555:  | 	{ | 
| 1556:  | 		if ($this->cachedInterfaces !== null) { | 
| 1557:  | 			return $this->cachedInterfaces; | 
| 1558:  | 		} | 
| 1559:  | 		$thisReflection = $this->getClassReflection(); | 
| 1560:  | 		if ($thisReflection === null) { | 
| 1561:  | 			return $this->cachedInterfaces = []; | 
| 1562:  | 		} | 
| 1563:  |  | 
| 1564:  | 		return $this->cachedInterfaces = array_map(static fn (ClassReflection $interfaceReflection): self => self::createFromReflection($interfaceReflection), $thisReflection->getInterfaces()); | 
| 1565:  | 	} | 
| 1566:  |  | 
| 1567:  | 	public function tryRemove(Type $typeToRemove): ?Type | 
| 1568:  | 	{ | 
| 1569:  | 		if ($typeToRemove instanceof ObjectType) { | 
| 1570:  | 			foreach (UnionType::EQUAL_UNION_CLASSES as $baseClass => $classes) { | 
| 1571:  | 				if ($this->getClassName() !== $baseClass) { | 
| 1572:  | 					continue; | 
| 1573:  | 				} | 
| 1574:  |  | 
| 1575:  | 				foreach ($classes as $index => $class) { | 
| 1576:  | 					if ($typeToRemove->getClassName() === $class) { | 
| 1577:  | 						unset($classes[$index]); | 
| 1578:  |  | 
| 1579:  | 						return TypeCombinator::union( | 
| 1580:  | 							...array_map(static fn (string $objectClass): Type => new ObjectType($objectClass), $classes), | 
| 1581:  | 						); | 
| 1582:  | 					} | 
| 1583:  | 				} | 
| 1584:  | 			} | 
| 1585:  | 		} | 
| 1586:  |  | 
| 1587:  | 		if ($this->isSuperTypeOf($typeToRemove)->yes()) { | 
| 1588:  | 			return $this->subtract($typeToRemove); | 
| 1589:  | 		} | 
| 1590:  |  | 
| 1591:  | 		return null; | 
| 1592:  | 	} | 
| 1593:  |  | 
| 1594:  | 	public function getFiniteTypes(): array | 
| 1595:  | 	{ | 
| 1596:  | 		return $this->getEnumCases(); | 
| 1597:  | 	} | 
| 1598:  |  | 
| 1599:  | 	public function exponentiate(Type $exponent): Type | 
| 1600:  | 	{ | 
| 1601:  | 		$object = new ObjectWithoutClassType(); | 
| 1602:  | 		if (!$exponent instanceof NeverType && !$object->isSuperTypeOf($this)->no() && !$object->isSuperTypeOf($exponent)->no()) { | 
| 1603:  | 			return TypeCombinator::union($this, $exponent); | 
| 1604:  | 		} | 
| 1605:  | 		return new ErrorType(); | 
| 1606:  | 	} | 
| 1607:  |  | 
| 1608:  | 	public function toPhpDocNode(): TypeNode | 
| 1609:  | 	{ | 
| 1610:  | 		return new IdentifierTypeNode($this->getClassName()); | 
| 1611:  | 	} | 
| 1612:  |  | 
| 1613:  | } | 
| 1614:  |  |