Skip to content

Commit

Permalink
More precise array_keys return type
Browse files Browse the repository at this point in the history
  • Loading branch information
staabm committed Jan 4, 2025
1 parent cdf5110 commit 9df457a
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 2 deletions.
23 changes: 21 additions & 2 deletions src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@
use PHPStan\Analyser\Scope;
use PHPStan\Php\PhpVersion;
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Type\Accessory\AccessoryArrayListType;
use PHPStan\Type\ArrayType;
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
use PHPStan\Type\GeneralizePrecision;
use PHPStan\Type\NeverType;
use PHPStan\Type\NullType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use function count;
use function strtolower;

Expand All @@ -27,7 +31,7 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo

public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type
{
if (count($functionCall->getArgs()) !== 1) {
if (count($functionCall->getArgs()) < 1) {
return null;
}

Expand All @@ -36,7 +40,22 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
return $this->phpVersion->arrayFunctionsReturnNullWithNonArray() ? new NullType() : new NeverType();
}

return $arrayType->getKeysArray();
$keysArray = $arrayType->getKeysArray();
if (count($functionCall->getArgs()) === 1) {
return $keysArray;
}

$newArrayType = $keysArray;
if (!$keysArray->isConstantArray()->no()) {
$newArrayType = new ArrayType(
$keysArray->getIterableKeyType()->generalize(GeneralizePrecision::lessSpecific()),
$keysArray->getIterableValueType()->generalize(GeneralizePrecision::lessSpecific()),
);
}
if ($keysArray->isList()->yes()) {
$newArrayType = TypeCombinator::intersect($newArrayType, new AccessoryArrayListType());
}
return $newArrayType;
}

}
51 changes: 51 additions & 0 deletions tests/PHPStan/Analyser/nsrt/bug-11928.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

namespace Bug11928;

use function PHPStan\Testing\assertType;

function doFoo()
{
$a = [2 => 1, 3 => 2, 4 => 1];

$keys = array_keys($a, 1); // returns [2, 4]
assertType('list<int>', $keys);

$keys = array_keys($a); // returns [2, 3, 4]
assertType('array{2, 3, 4}', $keys);
}

function doFooStrings() {
$a = [2 => 'hi', 3 => '123', 'xy' => 5];
$keys = array_keys($a, 1);
assertType('list<int|string>', $keys);

$keys = array_keys($a);
assertType("array{2, 3, 'xy'}", $keys);
}

/**
* @param array<int, int> $array
* @param list<int> $list
* @param array<string, string> $strings
* @return void
*/
function doFooBar(array $array, array $list, array $strings) {
$keys = array_keys($strings, "a", true);
assertType('list<string>', $keys);

$keys = array_keys($strings, "a", false);
assertType('list<string>', $keys);

$keys = array_keys($array, 1, true);
assertType('list<int>', $keys);

$keys = array_keys($array, 1, false);
assertType('list<int>', $keys);

$keys = array_keys($list, 1, true);
assertType('list<int<0, max>>', $keys);

$keys = array_keys($list, 1, true);
assertType('list<int<0, max>>', $keys);
}

0 comments on commit 9df457a

Please sign in to comment.