Skip to content

Commit

Permalink
Fix array_column() with null as $column_key
Browse files Browse the repository at this point in the history
  • Loading branch information
jlherren authored and ondrejmirtes committed Feb 1, 2022
1 parent b833073 commit 28179f2
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 0 deletions.
10 changes: 10 additions & 0 deletions src/Type/Php/ArrayColumnFunctionReturnTypeExtension.php
Expand Up @@ -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;
Expand Down Expand Up @@ -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 === []) {
Expand Down
1 change: 1 addition & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Expand Up @@ -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');
Expand Down
15 changes: 15 additions & 0 deletions tests/PHPStan/Analyser/data/array-column.php
Expand Up @@ -10,6 +10,7 @@ function testArrays(array $array): void
/** @var array<int, array<string, string>> $array */
assertType('array<int, string>', array_column($array, 'column'));
assertType('array<int|string, string>', array_column($array, 'column', 'key'));
assertType('array<int|string, array<string, string>>', array_column($array, null, 'key'));

/** @var non-empty-array<int, array<string, string>> $array */
// Note: Array may still be empty!
Expand All @@ -18,13 +19,15 @@ 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
{
/** @var array<int, array{column: string, key: string}> $array */
assertType('array<int, string>', array_column($array, 'column'));
assertType('array<string, string>', array_column($array, 'column', 'key'));
assertType('array<string, array{column: string, key: string}>', array_column($array, null, 'key'));

/** @var array<int, array{column: string, key: string}> $array */
assertType('array{}', array_column($array, 'foo'));
Expand All @@ -33,20 +36,24 @@ 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<string, string>", array_column($array, 'column', 'key'));
assertType("non-empty-array<string, array{column: string, key: string}>", array_column($array, null, 'key'));

/** @var array<int, array{column?: 'foo', key?: 'bar'}> $array */
assertType("array<int, 'foo'>", 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<int, array{column1: string, column2: bool}> $array */
assertType('array<int, bool|string>', array_column($array, mt_rand(0, 1) === 0 ? 'column1' : 'column2'));

/** @var non-empty-array<int, array{column: string, key: string}> $array */
assertType('non-empty-array<int, string>', array_column($array, 'column'));
assertType('non-empty-array<string, string>', array_column($array, 'column', 'key'));
assertType('non-empty-array<string, array{column: string, key: string}>', array_column($array, null, 'key'));
}

function testImprecise(array $array): void {
Expand All @@ -55,9 +62,11 @@ function testImprecise(array $array): void {
/** @var array{array{column?: 'foo', key: 'bar'}} $array */
assertType("array<int, 'foo'>", 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<int, array<string, string>> $array */
assertType('array<int, string>', array_column($array, 'column'));
Expand All @@ -72,21 +81,27 @@ function testObjects(array $array): void {
/** @var array<int, DOMElement> $array */
assertType('array<int, string>', array_column($array, 'nodeName'));
assertType('array<string, string>', array_column($array, 'nodeName', 'tagName'));
assertType('array<string, DOMElement>', array_column($array, null, 'tagName'));
assertType('array<int, mixed>', array_column($array, 'foo'));
assertType('array<string, mixed>', array_column($array, 'foo', 'tagName'));
assertType('array<string>', array_column($array, 'nodeName', 'foo'));
assertType('array<DOMElement>', array_column($array, null, 'foo'));

/** @var non-empty-array<int, DOMElement> $array */
assertType('non-empty-array<int, string>', array_column($array, 'nodeName'));
assertType('non-empty-array<string, string>', array_column($array, 'nodeName', 'tagName'));
assertType('non-empty-array<string, DOMElement>', array_column($array, null, 'tagName'));
assertType('array<int, mixed>', array_column($array, 'foo'));
assertType('array<string, mixed>', array_column($array, 'foo', 'tagName'));
assertType('non-empty-array<string>', array_column($array, 'nodeName', 'foo'));
assertType('non-empty-array<DOMElement>', array_column($array, null, 'foo'));

/** @var array{DOMElement} $array */
assertType('array{string}', array_column($array, 'nodeName'));
assertType('non-empty-array<string, string>', array_column($array, 'nodeName', 'tagName'));
assertType('non-empty-array<string, DOMElement>', array_column($array, null, 'tagName'));
assertType('array<int, mixed>', array_column($array, 'foo'));
assertType('array<string, mixed>', array_column($array, 'foo', 'tagName'));
assertType('non-empty-array<int|string, string>', array_column($array, 'nodeName', 'foo'));
assertType('non-empty-array<int|string, DOMElement>', array_column($array, null, 'foo'));
}
18 changes: 18 additions & 0 deletions tests/PHPStan/Analyser/data/bug-6497.php
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace Bug6497;

use function PHPStan\Testing\assertType;

function bug6497(): void {
/** @var array<int, array{foo: string, bar: int}> */
$array = [
['foo' => 'baz', 'bar' => 3],
];

$array2 = array_column($array, null, 'foo');

assertType('array<string, array{foo: string, bar: int}>', $array2);
}

0 comments on commit 28179f2

Please sign in to comment.