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