diff --git a/src/Type/Php/ArrayColumnFunctionReturnTypeExtension.php b/src/Type/Php/ArrayColumnFunctionReturnTypeExtension.php index 51d8999c2f..7c69f17dfd 100644 --- a/src/Type/Php/ArrayColumnFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayColumnFunctionReturnTypeExtension.php @@ -17,6 +17,7 @@ use PHPStan\Type\IntegerType; use PHPStan\Type\MixedType; use PHPStan\Type\NeverType; +use PHPStan\Type\NullType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; use function count; @@ -42,7 +43,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, $arrayType = $scope->getType($functionCall->getArgs()[0]->value); $columnType = $scope->getType($functionCall->getArgs()[1]->value); - $indexType = $numArgs >= 3 ? $scope->getType($functionCall->getArgs()[2]->value) : null; + $indexType = $numArgs >= 3 ? $scope->getType($functionCall->getArgs()[2]->value) : new NullType(); $constantArrayTypes = $arrayType->getConstantArrays(); if (count($constantArrayTypes) === 1) { @@ -55,7 +56,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, return $this->handleAnyArray($arrayType, $columnType, $indexType, $scope); } - private function handleAnyArray(Type $arrayType, Type $columnType, ?Type $indexType, Scope $scope): Type + private function handleAnyArray(Type $arrayType, Type $columnType, Type $indexType, Scope $scope): Type { $iterableAtLeastOnce = $arrayType->isIterableAtLeastOnce(); if ($iterableAtLeastOnce->no()) { @@ -77,7 +78,7 @@ private function handleAnyArray(Type $arrayType, Type $columnType, ?Type $indexT return new ConstantArrayType([], []); } - if ($indexType !== null) { + if (!$indexType->isNull()->yes()) { $type = $this->getOffsetOrProperty($iterableValueType, $indexType, $scope, false); if ($type !== null) { $returnKeyType = $type; @@ -98,14 +99,14 @@ private function handleAnyArray(Type $arrayType, Type $columnType, ?Type $indexT if ($iterableAtLeastOnce->yes()) { $returnType = TypeCombinator::intersect($returnType, new NonEmptyArrayType()); } - if ($indexType === null) { + if ($indexType->isNull()->yes()) { $returnType = TypeCombinator::intersect($returnType, new AccessoryArrayListType()); } return $returnType; } - private function handleConstantArray(ConstantArrayType $arrayType, Type $columnType, ?Type $indexType, Scope $scope): ?Type + private function handleConstantArray(ConstantArrayType $arrayType, Type $columnType, Type $indexType, Scope $scope): ?Type { $builder = ConstantArrayTypeBuilder::createEmpty(); @@ -118,7 +119,7 @@ private function handleConstantArray(ConstantArrayType $arrayType, Type $columnT continue; } - if ($indexType !== null) { + if (!$indexType->isNull()->yes()) { $type = $this->getOffsetOrProperty($iterableValueType, $indexType, $scope, false); if ($type !== null) { $keyType = $type; diff --git a/tests/PHPStan/Analyser/nsrt/array-column-php82.php b/tests/PHPStan/Analyser/nsrt/array-column-php82.php index 7f0a545edc..e55e7a38ba 100644 --- a/tests/PHPStan/Analyser/nsrt/array-column-php82.php +++ b/tests/PHPStan/Analyser/nsrt/array-column-php82.php @@ -13,6 +13,7 @@ class ArrayColumnTest public function testArray1(array $array): void { assertType('list', array_column($array, 'column')); + assertType('list', array_column($array, 'column', null)); assertType('array', array_column($array, 'column', 'key')); assertType('array>', array_column($array, null, 'key')); } @@ -22,12 +23,14 @@ public function testArray2(array $array): void { // Note: Array may still be empty! assertType('list', array_column($array, 'column')); + assertType('list', array_column($array, 'column', null)); } /** @param array{} $array */ public function testArray3(array $array): void { assertType('array{}', array_column($array, 'column')); + assertType('array{}', array_column($array, 'column', null)); assertType('array{}', array_column($array, 'column', 'key')); assertType('array{}', array_column($array, null, 'key')); } @@ -66,6 +69,7 @@ public function testArray8(array $array): void public function testConstantArray1(array $array): void { assertType('list', array_column($array, 'column')); + assertType('list', array_column($array, 'column', null)); assertType('array', array_column($array, 'column', 'key')); assertType('array', array_column($array, null, 'key')); } @@ -74,6 +78,7 @@ public function testConstantArray1(array $array): void public function testConstantArray2(array $array): void { assertType('array{}', array_column($array, 'foo')); + assertType('array{}', array_column($array, 'foo', null)); assertType('array{}', array_column($array, 'foo', 'key')); } @@ -81,6 +86,7 @@ public function testConstantArray2(array $array): void public function testConstantArray3(array $array): void { assertType("array{string}", array_column($array, 'column')); + assertType("array{string}", array_column($array, 'column', null)); assertType("array{bar: string}", array_column($array, 'column', 'key')); assertType("array{bar: array{column: string, key: 'bar'}}", array_column($array, null, 'key')); } @@ -96,6 +102,7 @@ public function testConstantArray4(array $array): void public function testConstantArray5(array $array): void { assertType("list<'foo'>", array_column($array, 'column')); + assertType("list<'foo'>", array_column($array, 'column', null)); assertType("array<'bar'|int, 'foo'>", array_column($array, 'column', 'key')); assertType("array<'bar'|int, array{column?: 'foo', key?: 'bar'}>", array_column($array, null, 'key')); } @@ -104,12 +111,14 @@ public function testConstantArray5(array $array): void public function testConstantArray6(array $array): void { assertType('list', array_column($array, mt_rand(0, 1) === 0 ? 'column1' : 'column2')); + assertType('list', array_column($array, mt_rand(0, 1) === 0 ? 'column1' : 'column2', null)); } /** @param non-empty-array $array */ public function testConstantArray7(array $array): void { assertType('non-empty-list', array_column($array, 'column')); + assertType('non-empty-list', array_column($array, 'column', null)); assertType('non-empty-array', array_column($array, 'column', 'key')); assertType('non-empty-array', array_column($array, null, 'key')); } @@ -142,6 +151,7 @@ public function testConstantArray11(array $array): void public function testConstantArray12(array $array): void { assertType("array{0?: 'foo'}", array_column($array, 'column')); + assertType("array{0?: 'foo'}", array_column($array, 'column', null)); assertType("array{bar?: 'foo'}", array_column($array, 'column', 'key')); } @@ -151,6 +161,7 @@ public function testConstantArray12(array $array): void public function testImprecise1(array $array): void { assertType("list<'foo'>", array_column($array, 'column')); + assertType("list<'foo'>", array_column($array, 'column', null)); assertType("array<'bar', 'foo'>", array_column($array, 'column', 'key')); assertType("array{bar: array{column?: 'foo', key: 'bar'}}", array_column($array, null, 'key')); } @@ -166,6 +177,7 @@ public function testImprecise2(array $array): void public function testImprecise3(array $array): void { assertType('list', array_column($array, 'column')); + assertType('list', array_column($array, 'column', null)); assertType('array', array_column($array, 'column', 'key')); } @@ -173,9 +185,11 @@ public function testImprecise3(array $array): void public function testImprecise5(array $array): void { assertType('list', array_column($array, 'nodeName')); + assertType('list', array_column($array, 'nodeName', null)); assertType('array', array_column($array, 'nodeName', 'tagName')); assertType('array', array_column($array, null, 'tagName')); assertType('list', array_column($array, 'foo')); + assertType('list', array_column($array, 'foo', null)); assertType('array', array_column($array, 'foo', 'tagName')); assertType('array', array_column($array, 'nodeName', 'foo')); assertType('array', array_column($array, null, 'foo')); @@ -185,9 +199,11 @@ public function testImprecise5(array $array): void public function testObjects1(array $array): void { assertType('non-empty-list', array_column($array, 'nodeName')); + assertType('non-empty-list', array_column($array, 'nodeName', null)); assertType('non-empty-array', array_column($array, 'nodeName', 'tagName')); assertType('non-empty-array', array_column($array, null, 'tagName')); assertType('list', array_column($array, 'foo')); + assertType('list', array_column($array, 'foo', null)); assertType('array', array_column($array, 'foo', 'tagName')); assertType('non-empty-array', array_column($array, 'nodeName', 'foo')); assertType('non-empty-array', array_column($array, null, 'foo')); @@ -197,9 +213,11 @@ public function testObjects1(array $array): void public function testObjects2(array $array): void { assertType('array{string}', array_column($array, 'nodeName')); + assertType('array{string}', array_column($array, 'nodeName', null)); assertType('non-empty-array', array_column($array, 'nodeName', 'tagName')); assertType('non-empty-array', array_column($array, null, 'tagName')); assertType('list', array_column($array, 'foo')); + assertType('list', array_column($array, 'foo', null)); assertType('array', array_column($array, 'foo', 'tagName')); assertType('non-empty-array', array_column($array, 'nodeName', 'foo')); assertType('non-empty-array', array_column($array, null, 'foo')); @@ -214,6 +232,7 @@ final class Foo public function doFoo(array $a): void { assertType('array{}', array_column($a, 'nodeName')); + assertType('array{}', array_column($a, 'nodeName', null)); assertType('array{}', array_column($a, 'nodeName', 'tagName')); } diff --git a/tests/PHPStan/Analyser/nsrt/array-column.php b/tests/PHPStan/Analyser/nsrt/array-column.php index ee4ad00527..4f830b96d9 100644 --- a/tests/PHPStan/Analyser/nsrt/array-column.php +++ b/tests/PHPStan/Analyser/nsrt/array-column.php @@ -13,6 +13,7 @@ class ArrayColumnTest public function testArray1(array $array): void { assertType('list', array_column($array, 'column')); + assertType('list', array_column($array, 'column', null)); assertType('array', array_column($array, 'column', 'key')); assertType('array>', array_column($array, null, 'key')); } @@ -22,12 +23,14 @@ public function testArray2(array $array): void { // Note: Array may still be empty! assertType('list', array_column($array, 'column')); + assertType('list', array_column($array, 'column', null)); } /** @param array{} $array */ public function testArray3(array $array): void { assertType('array{}', array_column($array, 'column')); + assertType('array{}', array_column($array, 'column', null)); assertType('array{}', array_column($array, 'column', 'key')); assertType('array{}', array_column($array, null, 'key')); } @@ -66,6 +69,7 @@ public function testArray8(array $array): void public function testConstantArray1(array $array): void { assertType('list', array_column($array, 'column')); + assertType('list', array_column($array, 'column', null)); assertType('array', array_column($array, 'column', 'key')); assertType('array', array_column($array, null, 'key')); } @@ -74,6 +78,7 @@ public function testConstantArray1(array $array): void public function testConstantArray2(array $array): void { assertType('array{}', array_column($array, 'foo')); + assertType('array{}', array_column($array, 'foo', null)); assertType('array{}', array_column($array, 'foo', 'key')); } @@ -81,6 +86,7 @@ public function testConstantArray2(array $array): void public function testConstantArray3(array $array): void { assertType("array{string}", array_column($array, 'column')); + assertType("array{string}", array_column($array, 'column', null)); assertType("array{bar: string}", array_column($array, 'column', 'key')); assertType("array{bar: array{column: string, key: 'bar'}}", array_column($array, null, 'key')); } @@ -96,6 +102,7 @@ public function testConstantArray4(array $array): void public function testConstantArray5(array $array): void { assertType("list<'foo'>", array_column($array, 'column')); + assertType("list<'foo'>", array_column($array, 'column', null)); assertType("array<'bar'|int, 'foo'>", array_column($array, 'column', 'key')); assertType("array<'bar'|int, array{column?: 'foo', key?: 'bar'}>", array_column($array, null, 'key')); } @@ -104,12 +111,14 @@ public function testConstantArray5(array $array): void public function testConstantArray6(array $array): void { assertType('list', array_column($array, mt_rand(0, 1) === 0 ? 'column1' : 'column2')); + assertType('list', array_column($array, mt_rand(0, 1) === 0 ? 'column1' : 'column2', null)); } /** @param non-empty-array $array */ public function testConstantArray7(array $array): void { assertType('non-empty-list', array_column($array, 'column')); + assertType('non-empty-list', array_column($array, 'column', null)); assertType('non-empty-array', array_column($array, 'column', 'key')); assertType('non-empty-array', array_column($array, null, 'key')); } @@ -142,6 +151,7 @@ public function testConstantArray11(array $array): void public function testConstantArray12(array $array): void { assertType("array{0?: 'foo'}", array_column($array, 'column')); + assertType("array{0?: 'foo'}", array_column($array, 'column', null)); assertType("array{bar?: 'foo'}", array_column($array, 'column', 'key')); } @@ -149,6 +159,7 @@ public function testConstantArray12(array $array): void public function testConstantArray13(array $array): void { assertType("array{0?: 'foo1'|'foo2', 1?: 'foo2'}", array_column($array, 'column')); + assertType("array{0?: 'foo1'|'foo2', 1?: 'foo2'}", array_column($array, 'column', null)); assertType("array{bar1?: 'foo1', bar2?: 'foo2'}", array_column($array, 'column', 'key')); } @@ -156,6 +167,7 @@ public function testConstantArray13(array $array): void public function testConstantArray14(array $array): void { assertType("array{0: 'foo1'|'foo2', 1?: 'foo2'}", array_column($array, 'column')); + assertType("array{0: 'foo1'|'foo2', 1?: 'foo2'}", array_column($array, 'column', null)); assertType("array{bar1?: 'foo1', bar2: 'foo2'}", array_column($array, 'column', 'key')); } @@ -165,6 +177,7 @@ public function testConstantArray14(array $array): void public function testImprecise1(array $array): void { assertType("list<'foo'>", array_column($array, 'column')); + assertType("list<'foo'>", array_column($array, 'column', null)); assertType("array<'bar', 'foo'>", array_column($array, 'column', 'key')); assertType("array{bar: array{column?: 'foo', key: 'bar'}}", array_column($array, null, 'key')); } @@ -180,6 +193,7 @@ public function testImprecise2(array $array): void public function testImprecise3(array $array): void { assertType('list', array_column($array, 'column')); + assertType('list', array_column($array, 'column', null)); assertType('array', array_column($array, 'column', 'key')); } @@ -187,9 +201,11 @@ public function testImprecise3(array $array): void public function testImprecise5(array $array): void { assertType('list', array_column($array, 'nodeName')); + assertType('list', array_column($array, 'nodeName', null)); assertType('array', array_column($array, 'nodeName', 'tagName')); assertType('array', array_column($array, null, 'tagName')); assertType('list', array_column($array, 'foo')); + assertType('list', array_column($array, 'foo', null)); assertType('array', array_column($array, 'foo', 'tagName')); assertType('array', array_column($array, 'nodeName', 'foo')); assertType('array', array_column($array, null, 'foo')); @@ -199,9 +215,11 @@ public function testImprecise5(array $array): void public function testObjects1(array $array): void { assertType('non-empty-list', array_column($array, 'nodeName')); + assertType('non-empty-list', array_column($array, 'nodeName', null)); assertType('non-empty-array', array_column($array, 'nodeName', 'tagName')); assertType('non-empty-array', array_column($array, null, 'tagName')); assertType('list', array_column($array, 'foo')); + assertType('list', array_column($array, 'foo', null)); assertType('array', array_column($array, 'foo', 'tagName')); assertType('non-empty-array', array_column($array, 'nodeName', 'foo')); assertType('non-empty-array', array_column($array, null, 'foo')); @@ -211,9 +229,11 @@ public function testObjects1(array $array): void public function testObjects2(array $array): void { assertType('array{string}', array_column($array, 'nodeName')); + assertType('array{string}', array_column($array, 'nodeName', null)); assertType('non-empty-array', array_column($array, 'nodeName', 'tagName')); assertType('non-empty-array', array_column($array, null, 'tagName')); assertType('list', array_column($array, 'foo')); + assertType('list', array_column($array, 'foo', null)); assertType('array', array_column($array, 'foo', 'tagName')); assertType('non-empty-array', array_column($array, 'nodeName', 'foo')); assertType('non-empty-array', array_column($array, null, 'foo')); diff --git a/tests/PHPStan/Analyser/nsrt/bug-12954.php b/tests/PHPStan/Analyser/nsrt/bug-12954.php new file mode 100644 index 0000000000..5fbc508799 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12954.php @@ -0,0 +1,38 @@ + [ + 'name' => 'ROLE_USER', + 'description' => 'User role' + ], + 28 => [ + 'name' => 'ROLE_ADMIN', + 'description' => 'Admin role' + ], + 43 => [ + 'name' => 'ROLE_SUPER_ADMIN', + 'description' => 'SUPER Admin role' + ], +]; + +$list = ['ROLE_USER', 'ROLE_ADMIN', 'ROLE_SUPER_ADMIN']; + +$result = array_column($plop, 'name', null); + +/** + * @param list $array + */ +function doSomething(array $array): void +{ + assertType('list', $array); +} + +doSomething($result); +doSomething($list); + +assertType('array{\'ROLE_USER\', \'ROLE_ADMIN\', \'ROLE_SUPER_ADMIN\'}', $result); +assertType('array{\'ROLE_USER\', \'ROLE_ADMIN\', \'ROLE_SUPER_ADMIN\'}', $list); diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index c5fce4e73a..e792412761 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -2084,4 +2084,10 @@ public function testBug12847(): void ]); } + public function testBug12954(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-12954.php'], []); + } + }