diff --git a/src/Type/Php/ArrayColumnFunctionReturnTypeExtension.php b/src/Type/Php/ArrayColumnFunctionReturnTypeExtension.php index 566487965e..627b2c21d0 100644 --- a/src/Type/Php/ArrayColumnFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayColumnFunctionReturnTypeExtension.php @@ -16,6 +16,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 PHPStan\Type\TypeUtils; @@ -132,8 +133,17 @@ private function handleConstantArray(ConstantArrayType $arrayType, Type $columnT private function getOffsetOrProperty(Type $type, Type $offsetOrProperty, Scope $scope, bool $allowMaybe): ?Type { + $offsetIsNull = (new NullType())->isSuperTypeOf($offsetOrProperty); + if ($offsetIsNull->yes()) { + return $type; + } + $returnTypes = []; + if ($offsetIsNull->maybe()) { + $returnTypes[] = $type; + } + if (!$type->canAccessProperties()->no()) { $propertyTypes = TypeUtils::getConstantStrings($offsetOrProperty); if ($propertyTypes === []) { diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index a30b5ef684..c10fa0f57c 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -651,6 +651,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4357.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5817.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/array-column.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6497.php'); if (PHP_VERSION_ID >= 70400) { yield from $this->gatherAssertTypes(__DIR__ . '/data/isset-coalesce-empty-type.php'); diff --git a/tests/PHPStan/Analyser/data/array-column.php b/tests/PHPStan/Analyser/data/array-column.php index 83e42cb12e..36430a1b9d 100644 --- a/tests/PHPStan/Analyser/data/array-column.php +++ b/tests/PHPStan/Analyser/data/array-column.php @@ -10,6 +10,7 @@ function testArrays(array $array): void /** @var array> $array */ assertType('array', array_column($array, 'column')); assertType('array', array_column($array, 'column', 'key')); + assertType('array>', array_column($array, null, 'key')); /** @var non-empty-array> $array */ // Note: Array may still be empty! @@ -18,6 +19,7 @@ function testArrays(array $array): void /** @var array{} $array */ assertType('array{}', array_column($array, 'column')); assertType('array{}', array_column($array, 'column', 'key')); + assertType('array{}', array_column($array, null, 'key')); } function testConstantArrays(array $array): void @@ -25,6 +27,7 @@ function testConstantArrays(array $array): void /** @var array $array */ assertType('array', array_column($array, 'column')); assertType('array', array_column($array, 'column', 'key')); + assertType('array', array_column($array, null, 'key')); /** @var array $array */ assertType('array{}', array_column($array, 'foo')); @@ -33,13 +36,16 @@ function testConstantArrays(array $array): void /** @var array{array{column: string, key: 'bar'}} $array */ assertType("array{string}", array_column($array, 'column')); assertType("array{bar: string}", array_column($array, 'column', 'key')); + assertType("array{bar: array{column: string, key: 'bar'}}", array_column($array, null, 'key')); /** @var array{array{column: string, key: string}} $array */ assertType("non-empty-array", array_column($array, 'column', 'key')); + assertType("non-empty-array", array_column($array, null, 'key')); /** @var array $array */ assertType("array", array_column($array, 'column')); assertType("array<'bar'|int, 'foo'>", array_column($array, 'column', 'key')); + assertType("array<'bar'|int, array{column?: 'foo', key?: 'bar'}>", array_column($array, null, 'key')); /** @var array $array */ assertType('array', array_column($array, mt_rand(0, 1) === 0 ? 'column1' : 'column2')); @@ -47,6 +53,7 @@ function testConstantArrays(array $array): void /** @var non-empty-array $array */ assertType('non-empty-array', array_column($array, 'column')); assertType('non-empty-array', array_column($array, 'column', 'key')); + assertType('non-empty-array', array_column($array, null, 'key')); } function testImprecise(array $array): void { @@ -55,9 +62,11 @@ function testImprecise(array $array): void { /** @var array{array{column?: 'foo', key: 'bar'}} $array */ assertType("array", array_column($array, 'column')); assertType("array<'bar', 'foo'>", array_column($array, 'column', 'key')); + assertType("array{bar: array{column?: 'foo', key: 'bar'}}", array_column($array, null, 'key')); /** @var array{array{column: 'foo', key?: 'bar'}} $array */ assertType("non-empty-array<'bar'|int, 'foo'>", array_column($array, 'column', 'key')); + assertType("non-empty-array<'bar'|int, array{column: 'foo', key?: 'bar'}>", array_column($array, null, 'key')); /** @var array{array{column: 'foo', key: 'bar'}}|array> $array */ assertType('array', array_column($array, 'column')); @@ -72,21 +81,27 @@ function testObjects(array $array): void { /** @var array $array */ assertType('array', array_column($array, 'nodeName')); assertType('array', array_column($array, 'nodeName', 'tagName')); + assertType('array', array_column($array, null, 'tagName')); assertType('array', array_column($array, 'foo')); assertType('array', array_column($array, 'foo', 'tagName')); assertType('array', array_column($array, 'nodeName', 'foo')); + assertType('array', array_column($array, null, 'foo')); /** @var non-empty-array $array */ assertType('non-empty-array', array_column($array, 'nodeName')); assertType('non-empty-array', array_column($array, 'nodeName', 'tagName')); + assertType('non-empty-array', array_column($array, null, 'tagName')); assertType('array', array_column($array, 'foo')); 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')); /** @var array{DOMElement} $array */ assertType('array{string}', array_column($array, 'nodeName')); assertType('non-empty-array', array_column($array, 'nodeName', 'tagName')); + assertType('non-empty-array', array_column($array, null, 'tagName')); assertType('array', array_column($array, 'foo')); 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/data/bug-6497.php b/tests/PHPStan/Analyser/data/bug-6497.php new file mode 100644 index 0000000000..23d08e4e6b --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-6497.php @@ -0,0 +1,18 @@ + */ + $array = [ + ['foo' => 'baz', 'bar' => 3], + ]; + + $array2 = array_column($array, null, 'foo'); + + assertType('array', $array2); +}