|    1:  | <?php declare(strict_types = 1); | 
|    2:  |  | 
|    3:  | namespace PHPStan\Type\Constant; | 
|    4:  |  | 
|    5:  | use PHPStan\Reflection\ClassMemberAccessAnswerer; | 
|    6:  | use PHPStan\Reflection\InaccessibleMethod; | 
|    7:  | use PHPStan\Reflection\ParametersAcceptor; | 
|    8:  | use PHPStan\Reflection\ReflectionProviderStaticAccessor; | 
|    9:  | use PHPStan\Reflection\TrivialParametersAcceptor; | 
|   10:  | use PHPStan\ShouldNotHappenException; | 
|   11:  | use PHPStan\TrinaryLogic; | 
|   12:  | use PHPStan\Type\Accessory\AccessoryArrayListType; | 
|   13:  | use PHPStan\Type\Accessory\HasOffsetValueType; | 
|   14:  | use PHPStan\Type\Accessory\NonEmptyArrayType; | 
|   15:  | use PHPStan\Type\ArrayType; | 
|   16:  | use PHPStan\Type\BooleanType; | 
|   17:  | use PHPStan\Type\CompoundType; | 
|   18:  | use PHPStan\Type\ConstantScalarType; | 
|   19:  | use PHPStan\Type\ConstantType; | 
|   20:  | use PHPStan\Type\ErrorType; | 
|   21:  | use PHPStan\Type\GeneralizePrecision; | 
|   22:  | use PHPStan\Type\Generic\GenericClassStringType; | 
|   23:  | use PHPStan\Type\Generic\TemplateMixedType; | 
|   24:  | use PHPStan\Type\Generic\TemplateTypeMap; | 
|   25:  | use PHPStan\Type\Generic\TemplateTypeVariance; | 
|   26:  | use PHPStan\Type\IntegerRangeType; | 
|   27:  | use PHPStan\Type\IntegerType; | 
|   28:  | use PHPStan\Type\IntersectionType; | 
|   29:  | use PHPStan\Type\MixedType; | 
|   30:  | use PHPStan\Type\NeverType; | 
|   31:  | use PHPStan\Type\ObjectType; | 
|   32:  | use PHPStan\Type\ObjectWithoutClassType; | 
|   33:  | use PHPStan\Type\StringType; | 
|   34:  | use PHPStan\Type\Type; | 
|   35:  | use PHPStan\Type\TypeCombinator; | 
|   36:  | use PHPStan\Type\TypeUtils; | 
|   37:  | use PHPStan\Type\UnionType; | 
|   38:  | use PHPStan\Type\VerbosityLevel; | 
|   39:  | use function array_keys; | 
|   40:  | use function array_map; | 
|   41:  | use function array_merge; | 
|   42:  | use function array_pop; | 
|   43:  | use function array_push; | 
|   44:  | use function array_reverse; | 
|   45:  | use function array_slice; | 
|   46:  | use function array_unique; | 
|   47:  | use function array_values; | 
|   48:  | use function assert; | 
|   49:  | use function count; | 
|   50:  | use function implode; | 
|   51:  | use function in_array; | 
|   52:  | use function is_int; | 
|   53:  | use function is_string; | 
|   54:  | use function min; | 
|   55:  | use function pow; | 
|   56:  | use function sort; | 
|   57:  | use function sprintf; | 
|   58:  | use function strpos; | 
|   59:  |  | 
|   60:  |  | 
|   61:  |  | 
|   62:  |  | 
|   63:  | class ConstantArrayType extends ArrayType implements ConstantType | 
|   64:  | { | 
|   65:  |  | 
|   66:  | 	private const DESCRIBE_LIMIT = 8; | 
|   67:  |  | 
|   68:  | 	 | 
|   69:  | 	private ?array $allArrays = null; | 
|   70:  |  | 
|   71:  | 	 | 
|   72:  | 	private array $nextAutoIndexes; | 
|   73:  |  | 
|   74:  | 	 | 
|   75:  |  | 
|   76:  |  | 
|   77:  |  | 
|   78:  |  | 
|   79:  |  | 
|   80:  |  | 
|   81:  | 	public function __construct( | 
|   82:  | 		private array $keyTypes, | 
|   83:  | 		private array $valueTypes, | 
|   84:  | 		int|array $nextAutoIndexes = [0], | 
|   85:  | 		private array $optionalKeys = [], | 
|   86:  | 		private bool $isList = false, | 
|   87:  | 	) | 
|   88:  | 	{ | 
|   89:  | 		assert(count($keyTypes) === count($valueTypes)); | 
|   90:  |  | 
|   91:  | 		if (is_int($nextAutoIndexes)) { | 
|   92:  | 			$nextAutoIndexes = [$nextAutoIndexes]; | 
|   93:  | 		} | 
|   94:  |  | 
|   95:  | 		$this->nextAutoIndexes = $nextAutoIndexes; | 
|   96:  |  | 
|   97:  | 		$keyTypesCount = count($this->keyTypes); | 
|   98:  | 		if ($keyTypesCount === 0) { | 
|   99:  | 			$keyType = new NeverType(true); | 
|  100:  | 			$this->isList = true; | 
|  101:  | 		} elseif ($keyTypesCount === 1) { | 
|  102:  | 			$keyType = $this->keyTypes[0]; | 
|  103:  | 		} else { | 
|  104:  | 			$keyType = new UnionType($this->keyTypes); | 
|  105:  | 		} | 
|  106:  |  | 
|  107:  | 		parent::__construct( | 
|  108:  | 			$keyType, | 
|  109:  | 			count($valueTypes) > 0 ? TypeCombinator::union(...$valueTypes) : new NeverType(true), | 
|  110:  | 		); | 
|  111:  | 	} | 
|  112:  |  | 
|  113:  | 	public function getConstantArrays(): array | 
|  114:  | 	{ | 
|  115:  | 		return [$this]; | 
|  116:  | 	} | 
|  117:  |  | 
|  118:  | 	 | 
|  119:  | 	public function isEmpty(): bool | 
|  120:  | 	{ | 
|  121:  | 		return count($this->keyTypes) === 0; | 
|  122:  | 	} | 
|  123:  |  | 
|  124:  | 	 | 
|  125:  |  | 
|  126:  |  | 
|  127:  | 	public function getNextAutoIndexes(): array | 
|  128:  | 	{ | 
|  129:  | 		return $this->nextAutoIndexes; | 
|  130:  | 	} | 
|  131:  |  | 
|  132:  | 	 | 
|  133:  |  | 
|  134:  |  | 
|  135:  | 	public function getNextAutoIndex(): int | 
|  136:  | 	{ | 
|  137:  | 		return $this->nextAutoIndexes[count($this->nextAutoIndexes) - 1]; | 
|  138:  | 	} | 
|  139:  |  | 
|  140:  | 	 | 
|  141:  |  | 
|  142:  |  | 
|  143:  | 	public function getOptionalKeys(): array | 
|  144:  | 	{ | 
|  145:  | 		return $this->optionalKeys; | 
|  146:  | 	} | 
|  147:  |  | 
|  148:  | 	 | 
|  149:  |  | 
|  150:  |  | 
|  151:  | 	public function getAllArrays(): array | 
|  152:  | 	{ | 
|  153:  | 		if ($this->allArrays !== null) { | 
|  154:  | 			return $this->allArrays; | 
|  155:  | 		} | 
|  156:  |  | 
|  157:  | 		if (count($this->optionalKeys) <= 10) { | 
|  158:  | 			$optionalKeysCombinations = $this->powerSet($this->optionalKeys); | 
|  159:  | 		} else { | 
|  160:  | 			$optionalKeysCombinations = [ | 
|  161:  | 				[], | 
|  162:  | 				$this->optionalKeys, | 
|  163:  | 			]; | 
|  164:  | 		} | 
|  165:  |  | 
|  166:  | 		$requiredKeys = []; | 
|  167:  | 		foreach (array_keys($this->keyTypes) as $i) { | 
|  168:  | 			if (in_array($i, $this->optionalKeys, true)) { | 
|  169:  | 				continue; | 
|  170:  | 			} | 
|  171:  | 			$requiredKeys[] = $i; | 
|  172:  | 		} | 
|  173:  |  | 
|  174:  | 		$arrays = []; | 
|  175:  | 		foreach ($optionalKeysCombinations as $combination) { | 
|  176:  | 			$keys = array_merge($requiredKeys, $combination); | 
|  177:  | 			sort($keys); | 
|  178:  |  | 
|  179:  | 			if ($this->isList && array_keys($keys) !== array_values($keys)) { | 
|  180:  | 				continue; | 
|  181:  | 			} | 
|  182:  |  | 
|  183:  | 			$builder = ConstantArrayTypeBuilder::createEmpty(); | 
|  184:  | 			foreach ($keys as $i) { | 
|  185:  | 				$builder->setOffsetValueType($this->keyTypes[$i], $this->valueTypes[$i]); | 
|  186:  | 			} | 
|  187:  |  | 
|  188:  | 			$array = $builder->getArray(); | 
|  189:  | 			if (!$array instanceof ConstantArrayType) { | 
|  190:  | 				throw new ShouldNotHappenException(); | 
|  191:  | 			} | 
|  192:  |  | 
|  193:  | 			$arrays[] = $array; | 
|  194:  | 		} | 
|  195:  |  | 
|  196:  | 		return $this->allArrays = $arrays; | 
|  197:  | 	} | 
|  198:  |  | 
|  199:  | 	 | 
|  200:  |  | 
|  201:  |  | 
|  202:  |  | 
|  203:  |  | 
|  204:  | 	private function powerSet(array $in): array | 
|  205:  | 	{ | 
|  206:  | 		$count = count($in); | 
|  207:  | 		$members = pow(2, $count); | 
|  208:  | 		$return = []; | 
|  209:  | 		for ($i = 0; $i < $members; $i++) { | 
|  210:  | 			$b = sprintf('%0' . $count . 'b', $i); | 
|  211:  | 			$out = []; | 
|  212:  | 			for ($j = 0; $j < $count; $j++) { | 
|  213:  | 				if ($b[$j] !== '1') { | 
|  214:  | 					continue; | 
|  215:  | 				} | 
|  216:  |  | 
|  217:  | 				$out[] = $in[$j]; | 
|  218:  | 			} | 
|  219:  | 			$return[] = $out; | 
|  220:  | 		} | 
|  221:  |  | 
|  222:  | 		return $return; | 
|  223:  | 	} | 
|  224:  |  | 
|  225:  | 	 | 
|  226:  |  | 
|  227:  |  | 
|  228:  | 	public function getKeyTypes(): array | 
|  229:  | 	{ | 
|  230:  | 		return $this->keyTypes; | 
|  231:  | 	} | 
|  232:  |  | 
|  233:  | 	 | 
|  234:  | 	public function getFirstKeyType(): Type | 
|  235:  | 	{ | 
|  236:  | 		return $this->getFirstIterableKeyType(); | 
|  237:  | 	} | 
|  238:  |  | 
|  239:  | 	 | 
|  240:  | 	public function getLastKeyType(): Type | 
|  241:  | 	{ | 
|  242:  | 		return $this->getLastIterableKeyType(); | 
|  243:  | 	} | 
|  244:  |  | 
|  245:  | 	 | 
|  246:  |  | 
|  247:  |  | 
|  248:  | 	public function getValueTypes(): array | 
|  249:  | 	{ | 
|  250:  | 		return $this->valueTypes; | 
|  251:  | 	} | 
|  252:  |  | 
|  253:  | 	 | 
|  254:  | 	public function getFirstValueType(): Type | 
|  255:  | 	{ | 
|  256:  | 		return $this->getFirstIterableValueType(); | 
|  257:  | 	} | 
|  258:  |  | 
|  259:  | 	 | 
|  260:  | 	public function getLastValueType(): Type | 
|  261:  | 	{ | 
|  262:  | 		return $this->getLastIterableValueType(); | 
|  263:  | 	} | 
|  264:  |  | 
|  265:  | 	public function isOptionalKey(int $i): bool | 
|  266:  | 	{ | 
|  267:  | 		return in_array($i, $this->optionalKeys, true); | 
|  268:  | 	} | 
|  269:  |  | 
|  270:  | 	public function accepts(Type $type, bool $strictTypes): TrinaryLogic | 
|  271:  | 	{ | 
|  272:  | 		if ($type instanceof MixedType && !$type instanceof TemplateMixedType) { | 
|  273:  | 			return $type->isAcceptedBy($this, $strictTypes); | 
|  274:  | 		} | 
|  275:  |  | 
|  276:  | 		if ($type instanceof self && count($this->keyTypes) === 0) { | 
|  277:  | 			return TrinaryLogic::createFromBoolean(count($type->keyTypes) === 0); | 
|  278:  | 		} | 
|  279:  |  | 
|  280:  | 		$result = TrinaryLogic::createYes(); | 
|  281:  | 		foreach ($this->keyTypes as $i => $keyType) { | 
|  282:  | 			$valueType = $this->valueTypes[$i]; | 
|  283:  | 			$hasOffset = $type->hasOffsetValueType($keyType); | 
|  284:  | 			if ($hasOffset->no()) { | 
|  285:  | 				if ($this->isOptionalKey($i)) { | 
|  286:  | 					continue; | 
|  287:  | 				} | 
|  288:  | 				return $hasOffset; | 
|  289:  | 			} | 
|  290:  | 			if ($hasOffset->maybe() && $this->isOptionalKey($i)) { | 
|  291:  | 				$hasOffset = TrinaryLogic::createYes(); | 
|  292:  | 			} | 
|  293:  |  | 
|  294:  | 			$result = $result->and($hasOffset); | 
|  295:  | 			$otherValueType = $type->getOffsetValueType($keyType); | 
|  296:  | 			$acceptsValue = $valueType->accepts($otherValueType, $strictTypes); | 
|  297:  | 			if ($acceptsValue->no()) { | 
|  298:  | 				return $acceptsValue; | 
|  299:  | 			} | 
|  300:  | 			$result = $result->and($acceptsValue); | 
|  301:  | 		} | 
|  302:  |  | 
|  303:  | 		$result = $result->and($type->isArray()); | 
|  304:  | 		if ($type->isOversizedArray()->yes()) { | 
|  305:  | 			if (!$result->no()) { | 
|  306:  | 				return TrinaryLogic::createYes(); | 
|  307:  | 			} | 
|  308:  | 		} | 
|  309:  |  | 
|  310:  | 		return $result; | 
|  311:  | 	} | 
|  312:  |  | 
|  313:  | 	public function isSuperTypeOf(Type $type): TrinaryLogic | 
|  314:  | 	{ | 
|  315:  | 		if ($type instanceof self) { | 
|  316:  | 			if (count($this->keyTypes) === 0) { | 
|  317:  | 				if (count($type->keyTypes) > 0) { | 
|  318:  | 					if (count($type->optionalKeys) > 0) { | 
|  319:  | 						return TrinaryLogic::createMaybe(); | 
|  320:  | 					} | 
|  321:  | 					return TrinaryLogic::createNo(); | 
|  322:  | 				} | 
|  323:  |  | 
|  324:  | 				return TrinaryLogic::createYes(); | 
|  325:  | 			} | 
|  326:  |  | 
|  327:  | 			$results = []; | 
|  328:  | 			foreach ($this->keyTypes as $i => $keyType) { | 
|  329:  | 				$hasOffset = $type->hasOffsetValueType($keyType); | 
|  330:  | 				if ($hasOffset->no()) { | 
|  331:  | 					if (!$this->isOptionalKey($i)) { | 
|  332:  | 						return TrinaryLogic::createNo(); | 
|  333:  | 					} | 
|  334:  |  | 
|  335:  | 					$results[] = TrinaryLogic::createMaybe(); | 
|  336:  | 					continue; | 
|  337:  | 				} elseif ($hasOffset->maybe() && !$this->isOptionalKey($i)) { | 
|  338:  | 					$results[] = TrinaryLogic::createMaybe(); | 
|  339:  | 				} | 
|  340:  |  | 
|  341:  | 				$isValueSuperType = $this->valueTypes[$i]->isSuperTypeOf($type->getOffsetValueType($keyType)); | 
|  342:  | 				if ($isValueSuperType->no()) { | 
|  343:  | 					return TrinaryLogic::createNo(); | 
|  344:  | 				} | 
|  345:  | 				$results[] = $isValueSuperType; | 
|  346:  | 			} | 
|  347:  |  | 
|  348:  | 			return TrinaryLogic::createYes()->and(...$results); | 
|  349:  | 		} | 
|  350:  |  | 
|  351:  | 		if ($type instanceof ArrayType) { | 
|  352:  | 			$result = TrinaryLogic::createMaybe(); | 
|  353:  | 			if (count($this->keyTypes) === 0) { | 
|  354:  | 				return $result; | 
|  355:  | 			} | 
|  356:  |  | 
|  357:  | 			return $result->and( | 
|  358:  | 				$this->getKeyType()->isSuperTypeOf($type->getKeyType()), | 
|  359:  | 				$this->getItemType()->isSuperTypeOf($type->getItemType()), | 
|  360:  | 			); | 
|  361:  | 		} | 
|  362:  |  | 
|  363:  | 		if ($type instanceof CompoundType) { | 
|  364:  | 			return $type->isSubTypeOf($this); | 
|  365:  | 		} | 
|  366:  |  | 
|  367:  | 		return TrinaryLogic::createNo(); | 
|  368:  | 	} | 
|  369:  |  | 
|  370:  | 	public function equals(Type $type): bool | 
|  371:  | 	{ | 
|  372:  | 		if (!$type instanceof self) { | 
|  373:  | 			return false; | 
|  374:  | 		} | 
|  375:  |  | 
|  376:  | 		if (count($this->keyTypes) !== count($type->keyTypes)) { | 
|  377:  | 			return false; | 
|  378:  | 		} | 
|  379:  |  | 
|  380:  | 		foreach ($this->keyTypes as $i => $keyType) { | 
|  381:  | 			$valueType = $this->valueTypes[$i]; | 
|  382:  | 			if (!$valueType->equals($type->valueTypes[$i])) { | 
|  383:  | 				return false; | 
|  384:  | 			} | 
|  385:  | 			if (!$keyType->equals($type->keyTypes[$i])) { | 
|  386:  | 				return false; | 
|  387:  | 			} | 
|  388:  | 		} | 
|  389:  |  | 
|  390:  | 		if ($this->optionalKeys !== $type->optionalKeys) { | 
|  391:  | 			return false; | 
|  392:  | 		} | 
|  393:  |  | 
|  394:  | 		return true; | 
|  395:  | 	} | 
|  396:  |  | 
|  397:  | 	public function isCallable(): TrinaryLogic | 
|  398:  | 	{ | 
|  399:  | 		$typeAndMethods = $this->findTypeAndMethodNames(); | 
|  400:  | 		if ($typeAndMethods === []) { | 
|  401:  | 			return TrinaryLogic::createNo(); | 
|  402:  | 		} | 
|  403:  |  | 
|  404:  | 		$results = array_map( | 
|  405:  | 			static fn (ConstantArrayTypeAndMethod $typeAndMethod): TrinaryLogic => $typeAndMethod->getCertainty(), | 
|  406:  | 			$typeAndMethods, | 
|  407:  | 		); | 
|  408:  |  | 
|  409:  | 		return TrinaryLogic::createYes()->and(...$results); | 
|  410:  | 	} | 
|  411:  |  | 
|  412:  | 	 | 
|  413:  |  | 
|  414:  |  | 
|  415:  | 	public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array | 
|  416:  | 	{ | 
|  417:  | 		$typeAndMethodNames = $this->findTypeAndMethodNames(); | 
|  418:  | 		if ($typeAndMethodNames === []) { | 
|  419:  | 			throw new ShouldNotHappenException(); | 
|  420:  | 		} | 
|  421:  |  | 
|  422:  | 		$acceptors = []; | 
|  423:  | 		foreach ($typeAndMethodNames as $typeAndMethodName) { | 
|  424:  | 			if ($typeAndMethodName->isUnknown() || !$typeAndMethodName->getCertainty()->yes()) { | 
|  425:  | 				$acceptors[] = new TrivialParametersAcceptor(); | 
|  426:  | 				continue; | 
|  427:  | 			} | 
|  428:  |  | 
|  429:  | 			$method = $typeAndMethodName->getType() | 
|  430:  | 				->getMethod($typeAndMethodName->getMethod(), $scope); | 
|  431:  |  | 
|  432:  | 			if (!$scope->canCallMethod($method)) { | 
|  433:  | 				$acceptors[] = new InaccessibleMethod($method); | 
|  434:  | 				continue; | 
|  435:  | 			} | 
|  436:  |  | 
|  437:  | 			array_push($acceptors, ...$method->getVariants()); | 
|  438:  | 		} | 
|  439:  |  | 
|  440:  | 		return $acceptors; | 
|  441:  | 	} | 
|  442:  |  | 
|  443:  | 	 | 
|  444:  | 	public function findTypeAndMethodName(): ?ConstantArrayTypeAndMethod | 
|  445:  | 	{ | 
|  446:  | 		if (count($this->keyTypes) !== 2) { | 
|  447:  | 			return null; | 
|  448:  | 		} | 
|  449:  |  | 
|  450:  | 		if ($this->keyTypes[0]->isSuperTypeOf(new ConstantIntegerType(0))->no()) { | 
|  451:  | 			return null; | 
|  452:  | 		} | 
|  453:  |  | 
|  454:  | 		if ($this->keyTypes[1]->isSuperTypeOf(new ConstantIntegerType(1))->no()) { | 
|  455:  | 			return null; | 
|  456:  | 		} | 
|  457:  |  | 
|  458:  | 		[$classOrObject, $method] = $this->valueTypes; | 
|  459:  |  | 
|  460:  | 		if (!$method instanceof ConstantStringType) { | 
|  461:  | 			return ConstantArrayTypeAndMethod::createUnknown(); | 
|  462:  | 		} | 
|  463:  |  | 
|  464:  | 		if ($classOrObject instanceof ConstantStringType) { | 
|  465:  | 			$reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); | 
|  466:  | 			if (!$reflectionProvider->hasClass($classOrObject->getValue())) { | 
|  467:  | 				return null; | 
|  468:  | 			} | 
|  469:  | 			$type = new ObjectType($reflectionProvider->getClass($classOrObject->getValue())->getName()); | 
|  470:  | 		} elseif ($classOrObject instanceof GenericClassStringType) { | 
|  471:  | 			$type = $classOrObject->getGenericType(); | 
|  472:  | 		} elseif ((new ObjectWithoutClassType())->isSuperTypeOf($classOrObject)->yes()) { | 
|  473:  | 			$type = $classOrObject; | 
|  474:  | 		} else { | 
|  475:  | 			return ConstantArrayTypeAndMethod::createUnknown(); | 
|  476:  | 		} | 
|  477:  |  | 
|  478:  | 		$has = $type->hasMethod($method->getValue()); | 
|  479:  | 		if (!$has->no()) { | 
|  480:  | 			if ($this->isOptionalKey(0) || $this->isOptionalKey(1)) { | 
|  481:  | 				$has = $has->and(TrinaryLogic::createMaybe()); | 
|  482:  | 			} | 
|  483:  |  | 
|  484:  | 			return ConstantArrayTypeAndMethod::createConcrete($type, $method->getValue(), $has); | 
|  485:  | 		} | 
|  486:  |  | 
|  487:  | 		return null; | 
|  488:  | 	} | 
|  489:  |  | 
|  490:  | 	 | 
|  491:  | 	public function findTypeAndMethodNames(): array | 
|  492:  | 	{ | 
|  493:  | 		if (count($this->keyTypes) !== 2) { | 
|  494:  | 			return []; | 
|  495:  | 		} | 
|  496:  |  | 
|  497:  | 		if ($this->keyTypes[0]->isSuperTypeOf(new ConstantIntegerType(0))->no()) { | 
|  498:  | 			return []; | 
|  499:  | 		} | 
|  500:  |  | 
|  501:  | 		if ($this->keyTypes[1]->isSuperTypeOf(new ConstantIntegerType(1))->no()) { | 
|  502:  | 			return []; | 
|  503:  | 		} | 
|  504:  |  | 
|  505:  | 		[$classOrObjects, $methods] = $this->valueTypes; | 
|  506:  | 		$classOrObjects = TypeUtils::flattenTypes($classOrObjects); | 
|  507:  | 		$methods = TypeUtils::flattenTypes($methods); | 
|  508:  |  | 
|  509:  | 		$typeAndMethods = []; | 
|  510:  | 		foreach ($classOrObjects as $classOrObject) { | 
|  511:  | 			if ($classOrObject instanceof ConstantStringType) { | 
|  512:  | 				$reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); | 
|  513:  | 				if (!$reflectionProvider->hasClass($classOrObject->getValue())) { | 
|  514:  | 					continue; | 
|  515:  | 				} | 
|  516:  | 				$type = new ObjectType($reflectionProvider->getClass($classOrObject->getValue())->getName()); | 
|  517:  | 			} elseif ($classOrObject instanceof GenericClassStringType) { | 
|  518:  | 				$type = $classOrObject->getGenericType(); | 
|  519:  | 			} elseif ((new ObjectWithoutClassType())->isSuperTypeOf($classOrObject)->yes()) { | 
|  520:  | 				$type = $classOrObject; | 
|  521:  | 			} else { | 
|  522:  | 				$typeAndMethods[] = ConstantArrayTypeAndMethod::createUnknown(); | 
|  523:  | 				continue; | 
|  524:  | 			} | 
|  525:  |  | 
|  526:  | 			foreach ($methods as $method) { | 
|  527:  | 				if (!$method instanceof ConstantStringType) { | 
|  528:  | 					$typeAndMethods[] = ConstantArrayTypeAndMethod::createUnknown(); | 
|  529:  | 					continue; | 
|  530:  | 				} | 
|  531:  |  | 
|  532:  | 				$has = $type->hasMethod($method->getValue()); | 
|  533:  | 				if ($has->no()) { | 
|  534:  | 					continue; | 
|  535:  | 				} | 
|  536:  |  | 
|  537:  | 				if ($this->isOptionalKey(0) || $this->isOptionalKey(1)) { | 
|  538:  | 					$has = $has->and(TrinaryLogic::createMaybe()); | 
|  539:  | 				} | 
|  540:  |  | 
|  541:  | 				$typeAndMethods[] = ConstantArrayTypeAndMethod::createConcrete($type, $method->getValue(), $has); | 
|  542:  | 			} | 
|  543:  | 		} | 
|  544:  |  | 
|  545:  | 		return $typeAndMethods; | 
|  546:  | 	} | 
|  547:  |  | 
|  548:  | 	public function hasOffsetValueType(Type $offsetType): TrinaryLogic | 
|  549:  | 	{ | 
|  550:  | 		$offsetType = $offsetType->toArrayKey(); | 
|  551:  | 		if ($offsetType instanceof UnionType) { | 
|  552:  | 			return TrinaryLogic::lazyExtremeIdentity($offsetType->getTypes(), fn (Type $innerType) => $this->hasOffsetValueType($innerType)); | 
|  553:  | 		} | 
|  554:  |  | 
|  555:  | 		$result = TrinaryLogic::createNo(); | 
|  556:  | 		foreach ($this->keyTypes as $i => $keyType) { | 
|  557:  | 			if ( | 
|  558:  | 				$keyType instanceof ConstantIntegerType | 
|  559:  | 				&& $offsetType instanceof StringType | 
|  560:  | 				&& !$offsetType instanceof ConstantStringType | 
|  561:  | 			) { | 
|  562:  | 				return TrinaryLogic::createMaybe(); | 
|  563:  | 			} | 
|  564:  |  | 
|  565:  | 			$has = $keyType->isSuperTypeOf($offsetType); | 
|  566:  | 			if ($has->yes()) { | 
|  567:  | 				if ($this->isOptionalKey($i)) { | 
|  568:  | 					return TrinaryLogic::createMaybe(); | 
|  569:  | 				} | 
|  570:  | 				return TrinaryLogic::createYes(); | 
|  571:  | 			} | 
|  572:  | 			if (!$has->maybe()) { | 
|  573:  | 				continue; | 
|  574:  | 			} | 
|  575:  |  | 
|  576:  | 			$result = TrinaryLogic::createMaybe(); | 
|  577:  | 		} | 
|  578:  |  | 
|  579:  | 		return $result; | 
|  580:  | 	} | 
|  581:  |  | 
|  582:  | 	public function getOffsetValueType(Type $offsetType): Type | 
|  583:  | 	{ | 
|  584:  | 		$offsetType = $offsetType->toArrayKey(); | 
|  585:  | 		$matchingValueTypes = []; | 
|  586:  | 		$all = true; | 
|  587:  | 		foreach ($this->keyTypes as $i => $keyType) { | 
|  588:  | 			if ($keyType->isSuperTypeOf($offsetType)->no()) { | 
|  589:  | 				$all = false; | 
|  590:  | 				continue; | 
|  591:  | 			} | 
|  592:  |  | 
|  593:  | 			$matchingValueTypes[] = $this->valueTypes[$i]; | 
|  594:  | 		} | 
|  595:  |  | 
|  596:  | 		if ($all) { | 
|  597:  | 			if (count($this->keyTypes) === 0) { | 
|  598:  | 				return new ErrorType(); | 
|  599:  | 			} | 
|  600:  |  | 
|  601:  | 			return $this->getIterableValueType(); | 
|  602:  | 		} | 
|  603:  |  | 
|  604:  | 		if (count($matchingValueTypes) > 0) { | 
|  605:  | 			$type = TypeCombinator::union(...$matchingValueTypes); | 
|  606:  | 			if ($type instanceof ErrorType) { | 
|  607:  | 				return new MixedType(); | 
|  608:  | 			} | 
|  609:  |  | 
|  610:  | 			return $type; | 
|  611:  | 		} | 
|  612:  |  | 
|  613:  | 		return new ErrorType();  | 
|  614:  | 	} | 
|  615:  |  | 
|  616:  | 	public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type | 
|  617:  | 	{ | 
|  618:  | 		$builder = ConstantArrayTypeBuilder::createFromConstantArray($this); | 
|  619:  | 		$builder->setOffsetValueType($offsetType, $valueType); | 
|  620:  |  | 
|  621:  | 		return $builder->getArray(); | 
|  622:  | 	} | 
|  623:  |  | 
|  624:  | 	public function unsetOffset(Type $offsetType): Type | 
|  625:  | 	{ | 
|  626:  | 		$offsetType = $offsetType->toArrayKey(); | 
|  627:  | 		if ($offsetType instanceof ConstantIntegerType || $offsetType instanceof ConstantStringType) { | 
|  628:  | 			foreach ($this->keyTypes as $i => $keyType) { | 
|  629:  | 				if ($keyType->getValue() !== $offsetType->getValue()) { | 
|  630:  | 					continue; | 
|  631:  | 				} | 
|  632:  |  | 
|  633:  | 				$keyTypes = $this->keyTypes; | 
|  634:  | 				unset($keyTypes[$i]); | 
|  635:  | 				$valueTypes = $this->valueTypes; | 
|  636:  | 				unset($valueTypes[$i]); | 
|  637:  |  | 
|  638:  | 				$newKeyTypes = []; | 
|  639:  | 				$newValueTypes = []; | 
|  640:  | 				$newOptionalKeys = []; | 
|  641:  |  | 
|  642:  | 				$k = 0; | 
|  643:  | 				foreach ($keyTypes as $j => $newKeyType) { | 
|  644:  | 					$newKeyTypes[] = $newKeyType; | 
|  645:  | 					$newValueTypes[] = $valueTypes[$j]; | 
|  646:  | 					if (in_array($j, $this->optionalKeys, true)) { | 
|  647:  | 						$newOptionalKeys[] = $k; | 
|  648:  | 					} | 
|  649:  | 					$k++; | 
|  650:  | 				} | 
|  651:  |  | 
|  652:  | 				return new self($newKeyTypes, $newValueTypes, $this->nextAutoIndexes, $newOptionalKeys, false); | 
|  653:  | 			} | 
|  654:  |  | 
|  655:  | 			return $this; | 
|  656:  | 		} | 
|  657:  |  | 
|  658:  | 		$constantScalars = TypeUtils::getConstantScalars($offsetType); | 
|  659:  | 		if (count($constantScalars) > 0) { | 
|  660:  | 			$optionalKeys = $this->optionalKeys; | 
|  661:  |  | 
|  662:  | 			foreach ($constantScalars as $constantScalar) { | 
|  663:  | 				$constantScalar = $constantScalar->toArrayKey(); | 
|  664:  | 				if (!$constantScalar instanceof ConstantIntegerType && !$constantScalar instanceof ConstantStringType) { | 
|  665:  | 					continue; | 
|  666:  | 				} | 
|  667:  |  | 
|  668:  | 				foreach ($this->keyTypes as $i => $keyType) { | 
|  669:  | 					if ($keyType->getValue() !== $constantScalar->getValue()) { | 
|  670:  | 						continue; | 
|  671:  | 					} | 
|  672:  |  | 
|  673:  | 					if (in_array($i, $optionalKeys, true)) { | 
|  674:  | 						continue 2; | 
|  675:  | 					} | 
|  676:  |  | 
|  677:  | 					$optionalKeys[] = $i; | 
|  678:  | 				} | 
|  679:  | 			} | 
|  680:  |  | 
|  681:  | 			return new self($this->keyTypes, $this->valueTypes, $this->nextAutoIndexes, $optionalKeys, false); | 
|  682:  | 		} | 
|  683:  |  | 
|  684:  | 		$optionalKeys = $this->optionalKeys; | 
|  685:  | 		$isList = $this->isList; | 
|  686:  | 		foreach ($this->keyTypes as $i => $keyType) { | 
|  687:  | 			if (!$offsetType->isSuperTypeOf($keyType)->yes()) { | 
|  688:  | 				continue; | 
|  689:  | 			} | 
|  690:  | 			$optionalKeys[] = $i; | 
|  691:  | 			$isList = false; | 
|  692:  | 		} | 
|  693:  | 		$optionalKeys = array_values(array_unique($optionalKeys)); | 
|  694:  |  | 
|  695:  | 		return new self($this->keyTypes, $this->valueTypes, $this->nextAutoIndexes, $optionalKeys, $isList); | 
|  696:  | 	} | 
|  697:  |  | 
|  698:  | 	public function fillKeysArray(Type $valueType): Type | 
|  699:  | 	{ | 
|  700:  | 		$builder = ConstantArrayTypeBuilder::createEmpty(); | 
|  701:  |  | 
|  702:  | 		foreach ($this->valueTypes as $i => $keyType) { | 
|  703:  | 			if ((new IntegerType())->isSuperTypeOf($keyType)->no()) { | 
|  704:  | 				$stringKeyType = $keyType->toString(); | 
|  705:  | 				if ($stringKeyType instanceof ErrorType) { | 
|  706:  | 					return $stringKeyType; | 
|  707:  | 				} | 
|  708:  |  | 
|  709:  | 				$builder->setOffsetValueType($stringKeyType, $valueType, $this->isOptionalKey($i)); | 
|  710:  | 			} else { | 
|  711:  | 				$builder->setOffsetValueType($keyType, $valueType, $this->isOptionalKey($i)); | 
|  712:  | 			} | 
|  713:  | 		} | 
|  714:  |  | 
|  715:  | 		return $builder->getArray(); | 
|  716:  | 	} | 
|  717:  |  | 
|  718:  | 	public function flipArray(): Type | 
|  719:  | 	{ | 
|  720:  | 		$builder = ConstantArrayTypeBuilder::createEmpty(); | 
|  721:  |  | 
|  722:  | 		foreach ($this->keyTypes as $i => $keyType) { | 
|  723:  | 			$valueType = $this->valueTypes[$i]; | 
|  724:  | 			$builder->setOffsetValueType( | 
|  725:  | 				$valueType->toArrayKey(), | 
|  726:  | 				$keyType, | 
|  727:  | 				$this->isOptionalKey($i), | 
|  728:  | 			); | 
|  729:  | 		} | 
|  730:  |  | 
|  731:  | 		return $builder->getArray(); | 
|  732:  | 	} | 
|  733:  |  | 
|  734:  | 	public function intersectKeyArray(Type $otherArraysType): Type | 
|  735:  | 	{ | 
|  736:  | 		$builder = ConstantArrayTypeBuilder::createEmpty(); | 
|  737:  |  | 
|  738:  | 		foreach ($this->keyTypes as $i => $keyType) { | 
|  739:  | 			$valueType = $this->valueTypes[$i]; | 
|  740:  | 			$has = $otherArraysType->hasOffsetValueType($keyType); | 
|  741:  | 			if ($has->no()) { | 
|  742:  | 				continue; | 
|  743:  | 			} | 
|  744:  | 			$builder->setOffsetValueType($keyType, $valueType, $this->isOptionalKey($i) || !$has->yes()); | 
|  745:  | 		} | 
|  746:  |  | 
|  747:  | 		return $builder->getArray(); | 
|  748:  | 	} | 
|  749:  |  | 
|  750:  | 	public function popArray(): Type | 
|  751:  | 	{ | 
|  752:  | 		return $this->removeLastElements(1); | 
|  753:  | 	} | 
|  754:  |  | 
|  755:  | 	public function searchArray(Type $needleType): Type | 
|  756:  | 	{ | 
|  757:  | 		$matches = []; | 
|  758:  | 		$hasIdenticalValue = false; | 
|  759:  |  | 
|  760:  | 		foreach ($this->valueTypes as $index => $valueType) { | 
|  761:  | 			$isNeedleSuperType = $valueType->isSuperTypeOf($needleType); | 
|  762:  | 			if ($isNeedleSuperType->no()) { | 
|  763:  | 				continue; | 
|  764:  | 			} | 
|  765:  |  | 
|  766:  | 			if ($needleType instanceof ConstantScalarType && $valueType instanceof ConstantScalarType | 
|  767:  | 				&& $needleType->getValue() === $valueType->getValue() | 
|  768:  | 				&& !$this->isOptionalKey($index) | 
|  769:  | 			) { | 
|  770:  | 				$hasIdenticalValue = true; | 
|  771:  | 			} | 
|  772:  |  | 
|  773:  | 			$matches[] = $this->keyTypes[$index]; | 
|  774:  | 		} | 
|  775:  |  | 
|  776:  | 		if (count($matches) > 0) { | 
|  777:  | 			if ($hasIdenticalValue) { | 
|  778:  | 				return TypeCombinator::union(...$matches); | 
|  779:  | 			} | 
|  780:  |  | 
|  781:  | 			return TypeCombinator::union(new ConstantBooleanType(false), ...$matches); | 
|  782:  | 		} | 
|  783:  |  | 
|  784:  | 		return new ConstantBooleanType(false); | 
|  785:  | 	} | 
|  786:  |  | 
|  787:  | 	public function shiftArray(): Type | 
|  788:  | 	{ | 
|  789:  | 		return $this->removeFirstElements(1); | 
|  790:  | 	} | 
|  791:  |  | 
|  792:  | 	public function shuffleArray(): Type | 
|  793:  | 	{ | 
|  794:  | 		$valuesArray = $this->getValuesArray(); | 
|  795:  |  | 
|  796:  | 		$isIterableAtLeastOnce = $valuesArray->isIterableAtLeastOnce(); | 
|  797:  | 		if ($isIterableAtLeastOnce->no()) { | 
|  798:  | 			return $valuesArray; | 
|  799:  | 		} | 
|  800:  |  | 
|  801:  | 		$generalizedArray = new ArrayType($valuesArray->getKeyType(), $valuesArray->getItemType()); | 
|  802:  |  | 
|  803:  | 		if ($isIterableAtLeastOnce->yes()) { | 
|  804:  | 			$generalizedArray = TypeCombinator::intersect($generalizedArray, new NonEmptyArrayType()); | 
|  805:  | 		} | 
|  806:  | 		if ($valuesArray->isList) { | 
|  807:  | 			$generalizedArray = AccessoryArrayListType::intersectWith($generalizedArray); | 
|  808:  | 		} | 
|  809:  |  | 
|  810:  | 		return $generalizedArray; | 
|  811:  | 	} | 
|  812:  |  | 
|  813:  | 	public function isIterableAtLeastOnce(): TrinaryLogic | 
|  814:  | 	{ | 
|  815:  | 		$keysCount = count($this->keyTypes); | 
|  816:  | 		if ($keysCount === 0) { | 
|  817:  | 			return TrinaryLogic::createNo(); | 
|  818:  | 		} | 
|  819:  |  | 
|  820:  | 		$optionalKeysCount = count($this->optionalKeys); | 
|  821:  | 		if ($optionalKeysCount < $keysCount) { | 
|  822:  | 			return TrinaryLogic::createYes(); | 
|  823:  | 		} | 
|  824:  |  | 
|  825:  | 		return TrinaryLogic::createMaybe(); | 
|  826:  | 	} | 
|  827:  |  | 
|  828:  | 	public function getArraySize(): Type | 
|  829:  | 	{ | 
|  830:  | 		$optionalKeysCount = count($this->optionalKeys); | 
|  831:  | 		$totalKeysCount = count($this->getKeyTypes()); | 
|  832:  | 		if ($optionalKeysCount === 0) { | 
|  833:  | 			return new ConstantIntegerType($totalKeysCount); | 
|  834:  | 		} | 
|  835:  |  | 
|  836:  | 		return IntegerRangeType::fromInterval($totalKeysCount - $optionalKeysCount, $totalKeysCount); | 
|  837:  | 	} | 
|  838:  |  | 
|  839:  | 	public function getFirstIterableKeyType(): Type | 
|  840:  | 	{ | 
|  841:  | 		$keyTypes = []; | 
|  842:  | 		foreach ($this->keyTypes as $i => $keyType) { | 
|  843:  | 			$keyTypes[] = $keyType; | 
|  844:  | 			if (!$this->isOptionalKey($i)) { | 
|  845:  | 				break; | 
|  846:  | 			} | 
|  847:  | 		} | 
|  848:  |  | 
|  849:  | 		return TypeCombinator::union(...$keyTypes); | 
|  850:  | 	} | 
|  851:  |  | 
|  852:  | 	public function getLastIterableKeyType(): Type | 
|  853:  | 	{ | 
|  854:  | 		$keyTypes = []; | 
|  855:  | 		for ($i = count($this->keyTypes) - 1; $i >= 0; $i--) { | 
|  856:  | 			$keyTypes[] = $this->keyTypes[$i]; | 
|  857:  | 			if (!$this->isOptionalKey($i)) { | 
|  858:  | 				break; | 
|  859:  | 			} | 
|  860:  | 		} | 
|  861:  |  | 
|  862:  | 		return TypeCombinator::union(...$keyTypes); | 
|  863:  | 	} | 
|  864:  |  | 
|  865:  | 	public function getFirstIterableValueType(): Type | 
|  866:  | 	{ | 
|  867:  | 		$valueTypes = []; | 
|  868:  | 		foreach ($this->valueTypes as $i => $valueType) { | 
|  869:  | 			$valueTypes[] = $valueType; | 
|  870:  | 			if (!$this->isOptionalKey($i)) { | 
|  871:  | 				break; | 
|  872:  | 			} | 
|  873:  | 		} | 
|  874:  |  | 
|  875:  | 		return TypeCombinator::union(...$valueTypes); | 
|  876:  | 	} | 
|  877:  |  | 
|  878:  | 	public function getLastIterableValueType(): Type | 
|  879:  | 	{ | 
|  880:  | 		$valueTypes = []; | 
|  881:  | 		for ($i = count($this->keyTypes) - 1; $i >= 0; $i--) { | 
|  882:  | 			$valueTypes[] = $this->valueTypes[$i]; | 
|  883:  | 			if (!$this->isOptionalKey($i)) { | 
|  884:  | 				break; | 
|  885:  | 			} | 
|  886:  | 		} | 
|  887:  |  | 
|  888:  | 		return TypeCombinator::union(...$valueTypes); | 
|  889:  | 	} | 
|  890:  |  | 
|  891:  | 	public function isConstantArray(): TrinaryLogic | 
|  892:  | 	{ | 
|  893:  | 		return TrinaryLogic::createYes(); | 
|  894:  | 	} | 
|  895:  |  | 
|  896:  | 	public function isList(): TrinaryLogic | 
|  897:  | 	{ | 
|  898:  | 		return TrinaryLogic::createFromBoolean($this->isList); | 
|  899:  | 	} | 
|  900:  |  | 
|  901:  | 	 | 
|  902:  | 	public function removeLast(): self | 
|  903:  | 	{ | 
|  904:  | 		return $this->removeLastElements(1); | 
|  905:  | 	} | 
|  906:  |  | 
|  907:  | 	 | 
|  908:  | 	private function removeLastElements(int $length): self | 
|  909:  | 	{ | 
|  910:  | 		$keyTypesCount = count($this->keyTypes); | 
|  911:  | 		if ($keyTypesCount === 0) { | 
|  912:  | 			return $this; | 
|  913:  | 		} | 
|  914:  |  | 
|  915:  | 		$keyTypes = $this->keyTypes; | 
|  916:  | 		$valueTypes = $this->valueTypes; | 
|  917:  | 		$optionalKeys = $this->optionalKeys; | 
|  918:  | 		$nextAutoindex = $this->nextAutoIndexes; | 
|  919:  |  | 
|  920:  | 		$optionalKeysRemoved = 0; | 
|  921:  | 		$newLength = $keyTypesCount - $length; | 
|  922:  | 		for ($i = $keyTypesCount - 1; $i >= 0; $i--) { | 
|  923:  | 			$isOptional = $this->isOptionalKey($i); | 
|  924:  |  | 
|  925:  | 			if ($i >= $newLength) { | 
|  926:  | 				if ($isOptional) { | 
|  927:  | 					$optionalKeysRemoved++; | 
|  928:  | 					foreach ($optionalKeys as $key => $value) { | 
|  929:  | 						if ($value === $i) { | 
|  930:  | 							unset($optionalKeys[$key]); | 
|  931:  | 							break; | 
|  932:  | 						} | 
|  933:  | 					} | 
|  934:  | 				} | 
|  935:  |  | 
|  936:  | 				$removedKeyType = array_pop($keyTypes); | 
|  937:  | 				array_pop($valueTypes); | 
|  938:  | 				$nextAutoindex = $removedKeyType instanceof ConstantIntegerType | 
|  939:  | 					? $removedKeyType->getValue() | 
|  940:  | 					: $this->getNextAutoIndex();  | 
|  941:  | 				continue; | 
|  942:  | 			} | 
|  943:  |  | 
|  944:  | 			if ($isOptional || $optionalKeysRemoved <= 0) { | 
|  945:  | 				continue; | 
|  946:  | 			} | 
|  947:  |  | 
|  948:  | 			$optionalKeys[] = $i; | 
|  949:  | 			$optionalKeysRemoved--; | 
|  950:  | 		} | 
|  951:  |  | 
|  952:  | 		return new self( | 
|  953:  | 			$keyTypes, | 
|  954:  | 			$valueTypes, | 
|  955:  | 			$nextAutoindex, | 
|  956:  | 			array_values($optionalKeys), | 
|  957:  | 			$this->isList, | 
|  958:  | 		); | 
|  959:  | 	} | 
|  960:  |  | 
|  961:  | 	 | 
|  962:  | 	public function removeFirst(): self | 
|  963:  | 	{ | 
|  964:  | 		return $this->removeFirstElements(1); | 
|  965:  | 	} | 
|  966:  |  | 
|  967:  | 	 | 
|  968:  | 	private function removeFirstElements(int $length, bool $reindex = true): self | 
|  969:  | 	{ | 
|  970:  | 		$builder = ConstantArrayTypeBuilder::createEmpty(); | 
|  971:  |  | 
|  972:  | 		$optionalKeysIgnored = 0; | 
|  973:  | 		foreach ($this->keyTypes as $i => $keyType) { | 
|  974:  | 			$isOptional = $this->isOptionalKey($i); | 
|  975:  | 			if ($i <= $length - 1) { | 
|  976:  | 				if ($isOptional) { | 
|  977:  | 					$optionalKeysIgnored++; | 
|  978:  | 				} | 
|  979:  | 				continue; | 
|  980:  | 			} | 
|  981:  |  | 
|  982:  | 			if (!$isOptional && $optionalKeysIgnored > 0) { | 
|  983:  | 				$isOptional = true; | 
|  984:  | 				$optionalKeysIgnored--; | 
|  985:  | 			} | 
|  986:  |  | 
|  987:  | 			$valueType = $this->valueTypes[$i]; | 
|  988:  | 			if ($reindex && $keyType instanceof ConstantIntegerType) { | 
|  989:  | 				$keyType = null; | 
|  990:  | 			} | 
|  991:  |  | 
|  992:  | 			$builder->setOffsetValueType($keyType, $valueType, $isOptional); | 
|  993:  | 		} | 
|  994:  |  | 
|  995:  | 		$array = $builder->getArray(); | 
|  996:  | 		if (!$array instanceof self) { | 
|  997:  | 			throw new ShouldNotHappenException(); | 
|  998:  | 		} | 
|  999:  |  | 
| 1000:  | 		return $array; | 
| 1001:  | 	} | 
| 1002:  |  | 
| 1003:  | 	public function slice(int $offset, ?int $limit, bool $preserveKeys = false): self | 
| 1004:  | 	{ | 
| 1005:  | 		$keyTypesCount = count($this->keyTypes); | 
| 1006:  | 		if ($keyTypesCount === 0) { | 
| 1007:  | 			return $this; | 
| 1008:  | 		} | 
| 1009:  |  | 
| 1010:  | 		$limit ??= $keyTypesCount; | 
| 1011:  | 		if ($limit < 0) { | 
| 1012:  | 			 | 
| 1013:  | 			return $this->removeLastElements($limit * -1) | 
| 1014:  | 				->slice($offset, null, $preserveKeys); | 
| 1015:  | 		} | 
| 1016:  |  | 
| 1017:  | 		if ($keyTypesCount + $offset <= 0) { | 
| 1018:  | 			 | 
| 1019:  | 			$offset = 0; | 
| 1020:  | 		} | 
| 1021:  |  | 
| 1022:  | 		if ($offset < 0) { | 
| 1023:  | 			 | 
| 1024:  |  | 
| 1025:  |  | 
| 1026:  |  | 
| 1027:  |  | 
| 1028:  |  | 
| 1029:  |  | 
| 1030:  |  | 
| 1031:  |  | 
| 1032:  |  | 
| 1033:  |  | 
| 1034:  |  | 
| 1035:  |  | 
| 1036:  | 			$offset *= -1; | 
| 1037:  | 			$reversedLimit = min($limit, $offset); | 
| 1038:  | 			$reversedOffset = $offset - $reversedLimit; | 
| 1039:  | 			return $this->reverse(true) | 
| 1040:  | 				->slice($reversedOffset, $reversedLimit, $preserveKeys) | 
| 1041:  | 				->reverse(true); | 
| 1042:  | 		} | 
| 1043:  |  | 
| 1044:  | 		if ($offset > 0) { | 
| 1045:  | 			return $this->removeFirstElements($offset, false) | 
| 1046:  | 				->slice(0, $limit, $preserveKeys); | 
| 1047:  | 		} | 
| 1048:  |  | 
| 1049:  | 		$builder = ConstantArrayTypeBuilder::createEmpty(); | 
| 1050:  |  | 
| 1051:  | 		$nonOptionalElementsCount = 0; | 
| 1052:  | 		$hasOptional = false; | 
| 1053:  | 		for ($i = 0; $nonOptionalElementsCount < $limit && $i < $keyTypesCount; $i++) { | 
| 1054:  | 			$isOptional = $this->isOptionalKey($i); | 
| 1055:  | 			if (!$isOptional) { | 
| 1056:  | 				$nonOptionalElementsCount++; | 
| 1057:  | 			} else { | 
| 1058:  | 				$hasOptional = true; | 
| 1059:  | 			} | 
| 1060:  |  | 
| 1061:  | 			$isLastElement = $nonOptionalElementsCount >= $limit || $i + 1 >= $keyTypesCount; | 
| 1062:  | 			if ($isLastElement && $limit < $keyTypesCount && $hasOptional) { | 
| 1063:  | 				 | 
| 1064:  | 				 | 
| 1065:  | 				 | 
| 1066:  | 				$isOptional = true; | 
| 1067:  | 			} | 
| 1068:  |  | 
| 1069:  | 			$builder->setOffsetValueType($this->keyTypes[$i], $this->valueTypes[$i], $isOptional); | 
| 1070:  | 		} | 
| 1071:  |  | 
| 1072:  | 		$slice = $builder->getArray(); | 
| 1073:  | 		if (!$slice instanceof self) { | 
| 1074:  | 			throw new ShouldNotHappenException(); | 
| 1075:  | 		} | 
| 1076:  |  | 
| 1077:  | 		return $preserveKeys ? $slice : $slice->reindex(); | 
| 1078:  | 	} | 
| 1079:  |  | 
| 1080:  | 	public function reverse(bool $preserveKeys = false): self | 
| 1081:  | 	{ | 
| 1082:  | 		$keyTypesReversed = array_reverse($this->keyTypes, true); | 
| 1083:  | 		$keyTypes = array_values($keyTypesReversed); | 
| 1084:  | 		$keyTypesReversedKeys = array_keys($keyTypesReversed); | 
| 1085:  | 		$optionalKeys = array_map(static fn (int $optionalKey): int => $keyTypesReversedKeys[$optionalKey], $this->optionalKeys); | 
| 1086:  |  | 
| 1087:  | 		$reversed = new self($keyTypes, array_reverse($this->valueTypes), $this->nextAutoIndexes, $optionalKeys, false); | 
| 1088:  |  | 
| 1089:  | 		return $preserveKeys ? $reversed : $reversed->reindex(); | 
| 1090:  | 	} | 
| 1091:  |  | 
| 1092:  | 	 | 
| 1093:  | 	public function chunk(int $length, bool $preserveKeys = false): self | 
| 1094:  | 	{ | 
| 1095:  | 		$builder = ConstantArrayTypeBuilder::createEmpty(); | 
| 1096:  |  | 
| 1097:  | 		$keyTypesCount = count($this->keyTypes); | 
| 1098:  | 		for ($i = 0; $i < $keyTypesCount; $i += $length) { | 
| 1099:  | 			$chunk = $this->slice($i, $length, true); | 
| 1100:  | 			$builder->setOffsetValueType(null, $preserveKeys ? $chunk : $chunk->getValuesArray()); | 
| 1101:  | 		} | 
| 1102:  |  | 
| 1103:  | 		$chunks = $builder->getArray(); | 
| 1104:  | 		if (!$chunks instanceof self) { | 
| 1105:  | 			throw new ShouldNotHappenException(); | 
| 1106:  | 		} | 
| 1107:  |  | 
| 1108:  | 		return $chunks; | 
| 1109:  | 	} | 
| 1110:  |  | 
| 1111:  | 	private function reindex(): self | 
| 1112:  | 	{ | 
| 1113:  | 		$keyTypes = []; | 
| 1114:  | 		$autoIndex = 0; | 
| 1115:  |  | 
| 1116:  | 		foreach ($this->keyTypes as $keyType) { | 
| 1117:  | 			if (!$keyType instanceof ConstantIntegerType) { | 
| 1118:  | 				$keyTypes[] = $keyType; | 
| 1119:  | 				continue; | 
| 1120:  | 			} | 
| 1121:  |  | 
| 1122:  | 			$keyTypes[] = new ConstantIntegerType($autoIndex); | 
| 1123:  | 			$autoIndex++; | 
| 1124:  | 		} | 
| 1125:  |  | 
| 1126:  | 		return new self($keyTypes, $this->valueTypes, [$autoIndex], $this->optionalKeys, true); | 
| 1127:  | 	} | 
| 1128:  |  | 
| 1129:  | 	public function toBoolean(): BooleanType | 
| 1130:  | 	{ | 
| 1131:  | 		return $this->getArraySize()->toBoolean(); | 
| 1132:  | 	} | 
| 1133:  |  | 
| 1134:  | 	public function toInteger(): Type | 
| 1135:  | 	{ | 
| 1136:  | 		return $this->toBoolean()->toInteger(); | 
| 1137:  | 	} | 
| 1138:  |  | 
| 1139:  | 	public function toFloat(): Type | 
| 1140:  | 	{ | 
| 1141:  | 		return $this->toBoolean()->toFloat(); | 
| 1142:  | 	} | 
| 1143:  |  | 
| 1144:  | 	public function generalize(GeneralizePrecision $precision): Type | 
| 1145:  | 	{ | 
| 1146:  | 		if (count($this->keyTypes) === 0) { | 
| 1147:  | 			return $this; | 
| 1148:  | 		} | 
| 1149:  |  | 
| 1150:  | 		if ($precision->isTemplateArgument()) { | 
| 1151:  | 			return $this->traverse(static fn (Type $type) => $type->generalize($precision)); | 
| 1152:  | 		} | 
| 1153:  |  | 
| 1154:  | 		$arrayType = new ArrayType( | 
| 1155:  | 			$this->getKeyType()->generalize($precision), | 
| 1156:  | 			$this->getItemType()->generalize($precision), | 
| 1157:  | 		); | 
| 1158:  |  | 
| 1159:  | 		$keyTypesCount = count($this->keyTypes); | 
| 1160:  | 		$optionalKeysCount = count($this->optionalKeys); | 
| 1161:  |  | 
| 1162:  | 		$accessoryTypes = []; | 
| 1163:  | 		if ($precision->isMoreSpecific() && ($keyTypesCount - $optionalKeysCount) < 32) { | 
| 1164:  | 			foreach ($this->keyTypes as $i => $keyType) { | 
| 1165:  | 				if ($this->isOptionalKey($i)) { | 
| 1166:  | 					continue; | 
| 1167:  | 				} | 
| 1168:  |  | 
| 1169:  | 				$accessoryTypes[] = new HasOffsetValueType($keyType, $this->valueTypes[$i]->generalize($precision)); | 
| 1170:  | 			} | 
| 1171:  | 		} elseif ($keyTypesCount > $optionalKeysCount) { | 
| 1172:  | 			$accessoryTypes[] = new NonEmptyArrayType(); | 
| 1173:  | 		} | 
| 1174:  |  | 
| 1175:  | 		if ($this->isList()->yes()) { | 
| 1176:  | 			$arrayType = AccessoryArrayListType::intersectWith($arrayType); | 
| 1177:  | 		} | 
| 1178:  |  | 
| 1179:  | 		if (count($accessoryTypes) > 0) { | 
| 1180:  | 			return TypeCombinator::intersect($arrayType, ...$accessoryTypes); | 
| 1181:  | 		} | 
| 1182:  |  | 
| 1183:  | 		return $arrayType; | 
| 1184:  | 	} | 
| 1185:  |  | 
| 1186:  | 	 | 
| 1187:  |  | 
| 1188:  |  | 
| 1189:  | 	public function generalizeValues(): ArrayType | 
| 1190:  | 	{ | 
| 1191:  | 		$valueTypes = []; | 
| 1192:  | 		foreach ($this->valueTypes as $valueType) { | 
| 1193:  | 			$valueTypes[] = $valueType->generalize(GeneralizePrecision::lessSpecific()); | 
| 1194:  | 		} | 
| 1195:  |  | 
| 1196:  | 		return new self($this->keyTypes, $valueTypes, $this->nextAutoIndexes, $this->optionalKeys, $this->isList); | 
| 1197:  | 	} | 
| 1198:  |  | 
| 1199:  | 	 | 
| 1200:  | 	public function generalizeToArray(): Type | 
| 1201:  | 	{ | 
| 1202:  | 		$isIterableAtLeastOnce = $this->isIterableAtLeastOnce(); | 
| 1203:  | 		if ($isIterableAtLeastOnce->no()) { | 
| 1204:  | 			return $this; | 
| 1205:  | 		} | 
| 1206:  |  | 
| 1207:  | 		$arrayType = new ArrayType($this->getKeyType(), $this->getItemType()); | 
| 1208:  |  | 
| 1209:  | 		if ($isIterableAtLeastOnce->yes()) { | 
| 1210:  | 			$arrayType = TypeCombinator::intersect($arrayType, new NonEmptyArrayType()); | 
| 1211:  | 		} | 
| 1212:  | 		if ($this->isList) { | 
| 1213:  | 			$arrayType = AccessoryArrayListType::intersectWith($arrayType); | 
| 1214:  | 		} | 
| 1215:  |  | 
| 1216:  | 		return $arrayType; | 
| 1217:  | 	} | 
| 1218:  |  | 
| 1219:  | 	 | 
| 1220:  |  | 
| 1221:  |  | 
| 1222:  | 	public function getKeysArray(): Type | 
| 1223:  | 	{ | 
| 1224:  | 		$keyTypes = []; | 
| 1225:  | 		$valueTypes = []; | 
| 1226:  | 		$optionalKeys = []; | 
| 1227:  | 		$autoIndex = 0; | 
| 1228:  |  | 
| 1229:  | 		foreach ($this->keyTypes as $i => $keyType) { | 
| 1230:  | 			$keyTypes[] = new ConstantIntegerType($i); | 
| 1231:  | 			$valueTypes[] = $keyType; | 
| 1232:  | 			$autoIndex++; | 
| 1233:  |  | 
| 1234:  | 			if (!$this->isOptionalKey($i)) { | 
| 1235:  | 				continue; | 
| 1236:  | 			} | 
| 1237:  |  | 
| 1238:  | 			$optionalKeys[] = $i; | 
| 1239:  | 		} | 
| 1240:  |  | 
| 1241:  | 		return new self($keyTypes, $valueTypes, $autoIndex, $optionalKeys, true); | 
| 1242:  | 	} | 
| 1243:  |  | 
| 1244:  | 	 | 
| 1245:  |  | 
| 1246:  |  | 
| 1247:  | 	public function getValuesArray(): Type | 
| 1248:  | 	{ | 
| 1249:  | 		$keyTypes = []; | 
| 1250:  | 		$valueTypes = []; | 
| 1251:  | 		$optionalKeys = []; | 
| 1252:  | 		$autoIndex = 0; | 
| 1253:  |  | 
| 1254:  | 		foreach ($this->valueTypes as $i => $valueType) { | 
| 1255:  | 			$keyTypes[] = new ConstantIntegerType($i); | 
| 1256:  | 			$valueTypes[] = $valueType; | 
| 1257:  | 			$autoIndex++; | 
| 1258:  |  | 
| 1259:  | 			if (!$this->isOptionalKey($i)) { | 
| 1260:  | 				continue; | 
| 1261:  | 			} | 
| 1262:  |  | 
| 1263:  | 			$optionalKeys[] = $i; | 
| 1264:  | 		} | 
| 1265:  |  | 
| 1266:  | 		return new self($keyTypes, $valueTypes, $autoIndex, $optionalKeys, true); | 
| 1267:  | 	} | 
| 1268:  |  | 
| 1269:  | 	 | 
| 1270:  | 	public function count(): Type | 
| 1271:  | 	{ | 
| 1272:  | 		return $this->getArraySize(); | 
| 1273:  | 	} | 
| 1274:  |  | 
| 1275:  | 	public function describe(VerbosityLevel $level): string | 
| 1276:  | 	{ | 
| 1277:  | 		$describeValue = function (bool $truncate) use ($level): string { | 
| 1278:  | 			$items = []; | 
| 1279:  | 			$values = []; | 
| 1280:  | 			$exportValuesOnly = true; | 
| 1281:  | 			foreach ($this->keyTypes as $i => $keyType) { | 
| 1282:  | 				$valueType = $this->valueTypes[$i]; | 
| 1283:  | 				if ($keyType->getValue() !== $i) { | 
| 1284:  | 					$exportValuesOnly = false; | 
| 1285:  | 				} | 
| 1286:  |  | 
| 1287:  | 				$isOptional = $this->isOptionalKey($i); | 
| 1288:  | 				if ($isOptional) { | 
| 1289:  | 					$exportValuesOnly = false; | 
| 1290:  | 				} | 
| 1291:  |  | 
| 1292:  | 				$keyDescription = $keyType->getValue(); | 
| 1293:  | 				if (is_string($keyDescription)) { | 
| 1294:  | 					if (strpos($keyDescription, '"') !== false) { | 
| 1295:  | 						$keyDescription = sprintf('\'%s\'', $keyDescription); | 
| 1296:  | 					} elseif (strpos($keyDescription, '\'') !== false) { | 
| 1297:  | 						$keyDescription = sprintf('"%s"', $keyDescription); | 
| 1298:  | 					} | 
| 1299:  | 				} | 
| 1300:  |  | 
| 1301:  | 				$valueTypeDescription = $valueType->describe($level); | 
| 1302:  | 				$items[] = sprintf('%s%s: %s', $keyDescription, $isOptional ? '?' : '', $valueTypeDescription); | 
| 1303:  | 				$values[] = $valueTypeDescription; | 
| 1304:  | 			} | 
| 1305:  |  | 
| 1306:  | 			$append = ''; | 
| 1307:  | 			if ($truncate && count($items) > self::DESCRIBE_LIMIT) { | 
| 1308:  | 				$items = array_slice($items, 0, self::DESCRIBE_LIMIT); | 
| 1309:  | 				$values = array_slice($values, 0, self::DESCRIBE_LIMIT); | 
| 1310:  | 				$append = ', ...'; | 
| 1311:  | 			} | 
| 1312:  |  | 
| 1313:  | 			return sprintf( | 
| 1314:  | 				'array{%s%s}', | 
| 1315:  | 				implode(', ', $exportValuesOnly ? $values : $items), | 
| 1316:  | 				$append, | 
| 1317:  | 			); | 
| 1318:  | 		}; | 
| 1319:  | 		return $level->handle( | 
| 1320:  | 			fn (): string => parent::describe($level), | 
| 1321:  | 			static fn (): string => $describeValue(true), | 
| 1322:  | 			static fn (): string => $describeValue(false), | 
| 1323:  | 		); | 
| 1324:  | 	} | 
| 1325:  |  | 
| 1326:  | 	public function inferTemplateTypes(Type $receivedType): TemplateTypeMap | 
| 1327:  | 	{ | 
| 1328:  | 		if ($receivedType instanceof UnionType || $receivedType instanceof IntersectionType) { | 
| 1329:  | 			return $receivedType->inferTemplateTypesOn($this); | 
| 1330:  | 		} | 
| 1331:  |  | 
| 1332:  | 		if ($receivedType instanceof self) { | 
| 1333:  | 			$typeMap = TemplateTypeMap::createEmpty(); | 
| 1334:  | 			foreach ($this->keyTypes as $i => $keyType) { | 
| 1335:  | 				$valueType = $this->valueTypes[$i]; | 
| 1336:  | 				if ($receivedType->hasOffsetValueType($keyType)->no()) { | 
| 1337:  | 					continue; | 
| 1338:  | 				} | 
| 1339:  | 				$receivedValueType = $receivedType->getOffsetValueType($keyType); | 
| 1340:  | 				$typeMap = $typeMap->union($valueType->inferTemplateTypes($receivedValueType)); | 
| 1341:  | 			} | 
| 1342:  |  | 
| 1343:  | 			return $typeMap; | 
| 1344:  | 		} | 
| 1345:  |  | 
| 1346:  | 		return parent::inferTemplateTypes($receivedType); | 
| 1347:  | 	} | 
| 1348:  |  | 
| 1349:  | 	public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array | 
| 1350:  | 	{ | 
| 1351:  | 		$variance = $positionVariance->compose(TemplateTypeVariance::createInvariant()); | 
| 1352:  | 		$references = []; | 
| 1353:  |  | 
| 1354:  | 		foreach ($this->keyTypes as $type) { | 
| 1355:  | 			foreach ($type->getReferencedTemplateTypes($variance) as $reference) { | 
| 1356:  | 				$references[] = $reference; | 
| 1357:  | 			} | 
| 1358:  | 		} | 
| 1359:  |  | 
| 1360:  | 		foreach ($this->valueTypes as $type) { | 
| 1361:  | 			foreach ($type->getReferencedTemplateTypes($variance) as $reference) { | 
| 1362:  | 				$references[] = $reference; | 
| 1363:  | 			} | 
| 1364:  | 		} | 
| 1365:  |  | 
| 1366:  | 		return $references; | 
| 1367:  | 	} | 
| 1368:  |  | 
| 1369:  | 	public function traverse(callable $cb): Type | 
| 1370:  | 	{ | 
| 1371:  | 		$valueTypes = []; | 
| 1372:  |  | 
| 1373:  | 		$stillOriginal = true; | 
| 1374:  | 		foreach ($this->valueTypes as $valueType) { | 
| 1375:  | 			$transformedValueType = $cb($valueType); | 
| 1376:  | 			if ($transformedValueType !== $valueType) { | 
| 1377:  | 				$stillOriginal = false; | 
| 1378:  | 			} | 
| 1379:  |  | 
| 1380:  | 			$valueTypes[] = $transformedValueType; | 
| 1381:  | 		} | 
| 1382:  |  | 
| 1383:  | 		if ($stillOriginal) { | 
| 1384:  | 			return $this; | 
| 1385:  | 		} | 
| 1386:  |  | 
| 1387:  | 		return new self($this->keyTypes, $valueTypes, $this->nextAutoIndexes, $this->optionalKeys, $this->isList); | 
| 1388:  | 	} | 
| 1389:  |  | 
| 1390:  | 	public function isKeysSupersetOf(self $otherArray): bool | 
| 1391:  | 	{ | 
| 1392:  | 		$keyTypesCount = count($this->keyTypes); | 
| 1393:  | 		$otherKeyTypesCount = count($otherArray->keyTypes); | 
| 1394:  |  | 
| 1395:  | 		if ($keyTypesCount < $otherKeyTypesCount) { | 
| 1396:  | 			return false; | 
| 1397:  | 		} | 
| 1398:  |  | 
| 1399:  | 		if ($otherKeyTypesCount === 0) { | 
| 1400:  | 			return $keyTypesCount === 0; | 
| 1401:  | 		} | 
| 1402:  |  | 
| 1403:  | 		$failOnDifferentValueType = $keyTypesCount !== $otherKeyTypesCount || $keyTypesCount < 2; | 
| 1404:  |  | 
| 1405:  | 		$keyTypes = $this->keyTypes; | 
| 1406:  |  | 
| 1407:  | 		foreach ($otherArray->keyTypes as $j => $keyType) { | 
| 1408:  | 			$i = self::findKeyIndex($keyType, $keyTypes); | 
| 1409:  | 			if ($i === null) { | 
| 1410:  | 				return false; | 
| 1411:  | 			} | 
| 1412:  |  | 
| 1413:  | 			unset($keyTypes[$i]); | 
| 1414:  |  | 
| 1415:  | 			$valueType = $this->valueTypes[$i]; | 
| 1416:  | 			$otherValueType = $otherArray->valueTypes[$j]; | 
| 1417:  | 			if (!$otherValueType->isSuperTypeOf($valueType)->no()) { | 
| 1418:  | 				continue; | 
| 1419:  | 			} | 
| 1420:  |  | 
| 1421:  | 			if ($failOnDifferentValueType) { | 
| 1422:  | 				return false; | 
| 1423:  | 			} | 
| 1424:  | 			$failOnDifferentValueType = true; | 
| 1425:  | 		} | 
| 1426:  |  | 
| 1427:  | 		$requiredKeyCount = 0; | 
| 1428:  | 		foreach (array_keys($keyTypes) as $i) { | 
| 1429:  | 			if ($this->isOptionalKey($i)) { | 
| 1430:  | 				continue; | 
| 1431:  | 			} | 
| 1432:  |  | 
| 1433:  | 			$requiredKeyCount++; | 
| 1434:  | 			if ($requiredKeyCount > 1) { | 
| 1435:  | 				return false; | 
| 1436:  | 			} | 
| 1437:  | 		} | 
| 1438:  |  | 
| 1439:  | 		return true; | 
| 1440:  | 	} | 
| 1441:  |  | 
| 1442:  | 	public function mergeWith(self $otherArray): self | 
| 1443:  | 	{ | 
| 1444:  | 		 | 
| 1445:  | 		$valueTypes = $this->valueTypes; | 
| 1446:  | 		$optionalKeys = $this->optionalKeys; | 
| 1447:  | 		foreach ($this->keyTypes as $i => $keyType) { | 
| 1448:  | 			$otherIndex = $otherArray->getKeyIndex($keyType); | 
| 1449:  | 			if ($otherIndex === null) { | 
| 1450:  | 				$optionalKeys[] = $i; | 
| 1451:  | 				continue; | 
| 1452:  | 			} | 
| 1453:  | 			if ($otherArray->isOptionalKey($otherIndex)) { | 
| 1454:  | 				$optionalKeys[] = $i; | 
| 1455:  | 			} | 
| 1456:  | 			$otherValueType = $otherArray->valueTypes[$otherIndex]; | 
| 1457:  | 			$valueTypes[$i] = TypeCombinator::union($valueTypes[$i], $otherValueType); | 
| 1458:  | 		} | 
| 1459:  |  | 
| 1460:  | 		$optionalKeys = array_values(array_unique($optionalKeys)); | 
| 1461:  |  | 
| 1462:  | 		$nextAutoIndexes = array_values(array_unique(array_merge($this->nextAutoIndexes, $otherArray->nextAutoIndexes))); | 
| 1463:  | 		sort($nextAutoIndexes); | 
| 1464:  |  | 
| 1465:  | 		return new self($this->keyTypes, $valueTypes, $nextAutoIndexes, $optionalKeys, $this->isList && $otherArray->isList); | 
| 1466:  | 	} | 
| 1467:  |  | 
| 1468:  | 	 | 
| 1469:  |  | 
| 1470:  |  | 
| 1471:  | 	private function getKeyIndex($otherKeyType): ?int | 
| 1472:  | 	{ | 
| 1473:  | 		return self::findKeyIndex($otherKeyType, $this->keyTypes); | 
| 1474:  | 	} | 
| 1475:  |  | 
| 1476:  | 	 | 
| 1477:  |  | 
| 1478:  |  | 
| 1479:  |  | 
| 1480:  | 	private static function findKeyIndex($otherKeyType, array $keyTypes): ?int | 
| 1481:  | 	{ | 
| 1482:  | 		foreach ($keyTypes as $i => $keyType) { | 
| 1483:  | 			if ($keyType->equals($otherKeyType)) { | 
| 1484:  | 				return $i; | 
| 1485:  | 			} | 
| 1486:  | 		} | 
| 1487:  |  | 
| 1488:  | 		return null; | 
| 1489:  | 	} | 
| 1490:  |  | 
| 1491:  | 	public function makeOffsetRequired(Type $offsetType): self | 
| 1492:  | 	{ | 
| 1493:  | 		$offsetType = $offsetType->toArrayKey(); | 
| 1494:  | 		$optionalKeys = $this->optionalKeys; | 
| 1495:  | 		foreach ($this->keyTypes as $i => $keyType) { | 
| 1496:  | 			if (!$keyType->equals($offsetType)) { | 
| 1497:  | 				continue; | 
| 1498:  | 			} | 
| 1499:  |  | 
| 1500:  | 			foreach ($optionalKeys as $j => $key) { | 
| 1501:  | 				if ($i === $key) { | 
| 1502:  | 					unset($optionalKeys[$j]); | 
| 1503:  | 					return new self($this->keyTypes, $this->valueTypes, $this->nextAutoIndexes, array_values($optionalKeys), $this->isList); | 
| 1504:  | 				} | 
| 1505:  | 			} | 
| 1506:  |  | 
| 1507:  | 			break; | 
| 1508:  | 		} | 
| 1509:  |  | 
| 1510:  | 		return $this; | 
| 1511:  | 	} | 
| 1512:  |  | 
| 1513:  | 	 | 
| 1514:  |  | 
| 1515:  |  | 
| 1516:  | 	public static function __set_state(array $properties): Type | 
| 1517:  | 	{ | 
| 1518:  | 		return new self($properties['keyTypes'], $properties['valueTypes'], $properties['nextAutoIndexes'] ?? $properties['nextAutoIndex'], $properties['optionalKeys'] ?? []); | 
| 1519:  | 	} | 
| 1520:  |  | 
| 1521:  | } | 
| 1522:  |  |