diff --git a/src/Type/Php/ArrayColumnFunctionReturnTypeExtension.php b/src/Type/Php/ArrayColumnFunctionReturnTypeExtension.php index 70f5892c2b..6827257179 100644 --- a/src/Type/Php/ArrayColumnFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayColumnFunctionReturnTypeExtension.php @@ -6,6 +6,7 @@ use PHPStan\Analyser\Scope; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\DynamicFunctionReturnTypeExtension; +use PHPStan\Type\NullType; use PHPStan\Type\Type; use function count; @@ -32,7 +33,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) { diff --git a/src/Type/Php/ArrayColumnHelper.php b/src/Type/Php/ArrayColumnHelper.php index a0796febf4..ef72623879 100644 --- a/src/Type/Php/ArrayColumnHelper.php +++ b/src/Type/Php/ArrayColumnHelper.php @@ -50,9 +50,9 @@ public function getReturnValueType(Type $arrayType, Type $columnType, Scope $sco return [$returnValueType, $iterableAtLeastOnce]; } - public function getReturnIndexType(Type $arrayType, ?Type $indexType, Scope $scope): Type + public function getReturnIndexType(Type $arrayType, Type $indexType, Scope $scope): Type { - if ($indexType !== null) { + if (!$indexType->isNull()->yes()) { $iterableValueType = $arrayType->getIterableValueType(); $type = $this->getOffsetOrProperty($iterableValueType, $indexType, $scope, false); @@ -69,7 +69,7 @@ public function getReturnIndexType(Type $arrayType, ?Type $indexType, Scope $sco return new IntegerType(); } - public function handleAnyArray(Type $arrayType, Type $columnType, ?Type $indexType, Scope $scope): Type + public function handleAnyArray(Type $arrayType, Type $columnType, Type $indexType, Scope $scope): Type { [$returnValueType, $iterableAtLeastOnce] = $this->getReturnValueType($arrayType, $columnType, $scope); if ($returnValueType instanceof NeverType) { @@ -82,14 +82,14 @@ public function handleAnyArray(Type $arrayType, Type $columnType, ?Type $indexTy if ($iterableAtLeastOnce->yes()) { $returnType = TypeCombinator::intersect($returnType, new NonEmptyArrayType()); } - if ($indexType === null) { + if ($indexType->isNull()->yes()) { $returnType = TypeCombinator::intersect($returnType, new AccessoryArrayListType()); } return $returnType; } - public function handleConstantArray(ConstantArrayType $arrayType, Type $columnType, ?Type $indexType, Scope $scope): ?Type + public function handleConstantArray(ConstantArrayType $arrayType, Type $columnType, Type $indexType, Scope $scope): ?Type { $builder = ConstantArrayTypeBuilder::createEmpty(); @@ -102,7 +102,7 @@ public function handleConstantArray(ConstantArrayType $arrayType, Type $columnTy 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 409535685f..bf8aba48a1 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -2242,4 +2242,10 @@ public function testBug12847(): void ]); } + public function testBug12954(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-12954.php'], []); + } + }