Skip to content

Commit

Permalink
Merge branch refs/heads/1.11.x into 1.12.x
Browse files Browse the repository at this point in the history
  • Loading branch information
phpstan-bot authored Aug 19, 2024
2 parents 7d43259 + f618124 commit 1846ffd
Show file tree
Hide file tree
Showing 3 changed files with 168 additions and 13 deletions.
55 changes: 43 additions & 12 deletions src/Analyser/TypeSpecifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -970,6 +970,11 @@ private function narrowUnionByArraySize(FuncCall $countFuncCall, UnionType $argT
if ($isSize->no()) {
continue;
}

$constArray = $this->turnListIntoConstantArray($countFuncCall, $innerType, $sizeType, $scope);
if ($constArray !== null) {
$innerType = $constArray;
}
}
if ($context->falsey()) {
if (!$isSize->yes()) {
Expand All @@ -986,6 +991,35 @@ private function narrowUnionByArraySize(FuncCall $countFuncCall, UnionType $argT
return null;
}

private function turnListIntoConstantArray(FuncCall $countFuncCall, Type $type, Type $sizeType, Scope $scope): ?Type
{
$argType = $scope->getType($countFuncCall->getArgs()[0]->value);

if (count($countFuncCall->getArgs()) === 1) {
$isNormalCount = TrinaryLogic::createYes();
} else {
$mode = $scope->getType($countFuncCall->getArgs()[1]->value);
$isNormalCount = (new ConstantIntegerType(COUNT_NORMAL))->isSuperTypeOf($mode)->or($argType->getIterableValueType()->isArray()->negate());
}

if (
$isNormalCount->yes()
&& $type->isList()->yes()
&& $sizeType instanceof ConstantIntegerType
&& $sizeType->getValue() < ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT
) {
// turn optional offsets non-optional
$valueTypesBuilder = ConstantArrayTypeBuilder::createEmpty();
for ($i = 0; $i < $sizeType->getValue(); $i++) {
$offsetType = new ConstantIntegerType($i);
$valueTypesBuilder->setOffsetValueType($offsetType, $type->getOffsetValueType($offsetType));
}
return $valueTypesBuilder->getArray();
}

return null;
}

private function specifyTypesForConstantBinaryExpression(
Expr $exprNode,
ConstantScalarType $constantType,
Expand Down Expand Up @@ -1050,21 +1084,18 @@ private function specifyTypesForConstantBinaryExpression(
}

if ($argType->isArray()->yes()) {
if (count($exprNode->getArgs()) === 1) {
$isNormalCount = TrinaryLogic::createYes();
} else {
$mode = $scope->getType($exprNode->getArgs()[1]->value);
$isNormalCount = (new ConstantIntegerType(COUNT_NORMAL))->isSuperTypeOf($mode)->or($argType->getIterableValueType()->isArray()->negate());
if (
$context->truthy()
&& $argType->isConstantArray()->yes()
&& $constantType->isSuperTypeOf($argType->getArraySize())->no()
) {
return $this->create($exprNode->getArgs()[0]->value, new NeverType(), $context, false, $scope, $rootExpr);
}

$funcTypes = $this->create($exprNode, $constantType, $context, false, $scope, $rootExpr);
if ($isNormalCount->yes() && $argType->isList()->yes() && $context->truthy() && $constantType->getValue() < ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT) {
$valueTypesBuilder = ConstantArrayTypeBuilder::createEmpty();
$itemType = $argType->getIterableValueType();
for ($i = 0; $i < $constantType->getValue(); $i++) {
$valueTypesBuilder->setOffsetValueType(new ConstantIntegerType($i), $itemType);
}
$valueTypes = $this->create($exprNode->getArgs()[0]->value, $valueTypesBuilder->getArray(), $context, false, $scope, $rootExpr);
$constArray = $this->turnListIntoConstantArray($exprNode, $argType, $constantType, $scope);
if ($context->truthy() && $constArray !== null) {
$valueTypes = $this->create($exprNode->getArgs()[0]->value, $constArray, $context, false, $scope, $rootExpr);
} else {
$valueTypes = $this->create($exprNode->getArgs()[0]->value, new NonEmptyArrayType(), $newContext, false, $scope, $rootExpr);
}
Expand Down
124 changes: 124 additions & 0 deletions tests/PHPStan/Analyser/nsrt/list-count.php
Original file line number Diff line number Diff line change
Expand Up @@ -216,3 +216,127 @@ function countCountable(CountableFoo $x, int $mode)
}
assertType('ListCount\CountableFoo', $x);
}

class CountWithOptionalKeys
{
/**
* @param array{0: mixed, 1?: string|null} $row
*/
protected function testOptionalKeys($row): void
{
if (count($row) === 0) {
assertType('*NEVER*', $row);
} else {
assertType('array{0: mixed, 1?: string|null}', $row);
}

if (count($row) === 1) {
assertType('array{mixed}', $row);
} else {
assertType('array{0: mixed, 1?: string|null}', $row);
}

if (count($row) === 2) {
assertType('array{mixed, string|null}', $row);
} else {
assertType('array{0: mixed, 1?: string|null}', $row);
}

if (count($row) === 3) {
assertType('*NEVER*', $row);
} else {
assertType('array{0: mixed, 1?: string|null}', $row);
}
}

/**
* @param array{mixed}|array{0: mixed, 1?: string|null} $row
*/
protected function testOptionalKeysInUnion($row): void
{
if (count($row) === 0) {
assertType('*NEVER*', $row);
} else {
assertType('array{0: mixed, 1?: string|null}', $row);
}

if (count($row) === 1) {
assertType('array{mixed}', $row);
} else {
assertType('array{0: mixed, 1?: string|null}', $row);
}

if (count($row) === 2) {
assertType('array{mixed, string|null}', $row);
} else {
assertType('array{0: mixed, 1?: string|null}', $row);
}

if (count($row) === 3) {
assertType('*NEVER*', $row);
} else {
assertType('array{0: mixed, 1?: string|null}', $row);
}
}

/**
* @param array{string}|array{0: int, 1?: string|null} $row
*/
protected function testOptionalKeysInListsOfTaggedUnion($row): void
{
if (count($row) === 0) {
assertType('*NEVER*', $row);
} else {
assertType('array{0: int, 1?: string|null}|array{string}', $row);
}

if (count($row) === 1) {
assertType('array{0: int, 1?: string|null}|array{string}', $row);
} else {
assertType('array{0: int, 1?: string|null}', $row);
}

if (count($row) === 2) {
assertType('array{int, string|null}', $row);
} else {
assertType('array{0: int, 1?: string|null}|array{string}', $row);
}

if (count($row) === 3) {
assertType('*NEVER*', $row);
} else {
assertType('array{0: int, 1?: string|null}|array{string}', $row);
}
}

/**
* @param array{string}|array{0: int, 3?: string|null} $row
*/
protected function testOptionalKeysInUnionArray($row): void
{
if (count($row) === 0) {
assertType('*NEVER*', $row);
} else {
assertType('array{0: int, 3?: string|null}|array{string}', $row);
}

if (count($row) === 1) {
assertType('array{0: int, 3?: string|null}|array{string}', $row);
} else {
assertType('array{0: int, 3?: string|null}', $row);
}

if (count($row) === 2) {
assertType('array{0: int, 3?: string|null}', $row);
} else {
assertType('array{0: int, 3?: string|null}|array{string}', $row);
}

if (count($row) === 3) {
assertType('*NEVER*', $row);
} else {
assertType('array{0: int, 3?: string|null}|array{string}', $row);
}
}

}
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/nsrt/preg_match_shapes.php
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ function bug11277a(string $value): void
if (preg_match('/^\[(.+,?)*\]$/', $value, $matches)) {
assertType('array{0: string, 1?: non-empty-string}', $matches);
if (count($matches) === 2) {
assertType('array{string, string}', $matches); // could be array{string, non-empty-string}
assertType('array{string, non-empty-string}', $matches);
}
}
}
Expand Down

0 comments on commit 1846ffd

Please sign in to comment.