Skip to content

Commit 179a727

Browse files
committed
Fix array_column() with explicit null $index_key
1 parent ea7072c commit 179a727

File tree

5 files changed

+90
-6
lines changed

5 files changed

+90
-6
lines changed

src/Type/Php/ArrayColumnFunctionReturnTypeExtension.php

+7-6
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use PHPStan\Type\IntegerType;
1818
use PHPStan\Type\MixedType;
1919
use PHPStan\Type\NeverType;
20+
use PHPStan\Type\NullType;
2021
use PHPStan\Type\Type;
2122
use PHPStan\Type\TypeCombinator;
2223
use function count;
@@ -42,7 +43,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
4243

4344
$arrayType = $scope->getType($functionCall->getArgs()[0]->value);
4445
$columnType = $scope->getType($functionCall->getArgs()[1]->value);
45-
$indexType = $numArgs >= 3 ? $scope->getType($functionCall->getArgs()[2]->value) : null;
46+
$indexType = $numArgs >= 3 ? $scope->getType($functionCall->getArgs()[2]->value) : new NullType();
4647

4748
$constantArrayTypes = $arrayType->getConstantArrays();
4849
if (count($constantArrayTypes) === 1) {
@@ -55,7 +56,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
5556
return $this->handleAnyArray($arrayType, $columnType, $indexType, $scope);
5657
}
5758

58-
private function handleAnyArray(Type $arrayType, Type $columnType, ?Type $indexType, Scope $scope): Type
59+
private function handleAnyArray(Type $arrayType, Type $columnType, Type $indexType, Scope $scope): Type
5960
{
6061
$iterableAtLeastOnce = $arrayType->isIterableAtLeastOnce();
6162
if ($iterableAtLeastOnce->no()) {
@@ -77,7 +78,7 @@ private function handleAnyArray(Type $arrayType, Type $columnType, ?Type $indexT
7778
return new ConstantArrayType([], []);
7879
}
7980

80-
if ($indexType !== null) {
81+
if (!$indexType->isNull()->yes()) {
8182
$type = $this->getOffsetOrProperty($iterableValueType, $indexType, $scope, false);
8283
if ($type !== null) {
8384
$returnKeyType = $type;
@@ -98,14 +99,14 @@ private function handleAnyArray(Type $arrayType, Type $columnType, ?Type $indexT
9899
if ($iterableAtLeastOnce->yes()) {
99100
$returnType = TypeCombinator::intersect($returnType, new NonEmptyArrayType());
100101
}
101-
if ($indexType === null) {
102+
if ($indexType->isNull()->yes()) {
102103
$returnType = TypeCombinator::intersect($returnType, new AccessoryArrayListType());
103104
}
104105

105106
return $returnType;
106107
}
107108

108-
private function handleConstantArray(ConstantArrayType $arrayType, Type $columnType, ?Type $indexType, Scope $scope): ?Type
109+
private function handleConstantArray(ConstantArrayType $arrayType, Type $columnType, Type $indexType, Scope $scope): ?Type
109110
{
110111
$builder = ConstantArrayTypeBuilder::createEmpty();
111112

@@ -118,7 +119,7 @@ private function handleConstantArray(ConstantArrayType $arrayType, Type $columnT
118119
continue;
119120
}
120121

121-
if ($indexType !== null) {
122+
if (!$indexType->isNull()->yes()) {
122123
$type = $this->getOffsetOrProperty($iterableValueType, $indexType, $scope, false);
123124
if ($type !== null) {
124125
$keyType = $type;

tests/PHPStan/Analyser/nsrt/array-column-php82.php

+19
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ class ArrayColumnTest
1313
public function testArray1(array $array): void
1414
{
1515
assertType('list<string>', array_column($array, 'column'));
16+
assertType('list<string>', array_column($array, 'column', null));
1617
assertType('array<int|string, string>', array_column($array, 'column', 'key'));
1718
assertType('array<int|string, array<string, string>>', array_column($array, null, 'key'));
1819
}
@@ -22,12 +23,14 @@ public function testArray2(array $array): void
2223
{
2324
// Note: Array may still be empty!
2425
assertType('list<string>', array_column($array, 'column'));
26+
assertType('list<string>', array_column($array, 'column', null));
2527
}
2628

2729
/** @param array{} $array */
2830
public function testArray3(array $array): void
2931
{
3032
assertType('array{}', array_column($array, 'column'));
33+
assertType('array{}', array_column($array, 'column', null));
3134
assertType('array{}', array_column($array, 'column', 'key'));
3235
assertType('array{}', array_column($array, null, 'key'));
3336
}
@@ -66,6 +69,7 @@ public function testArray8(array $array): void
6669
public function testConstantArray1(array $array): void
6770
{
6871
assertType('list<string>', array_column($array, 'column'));
72+
assertType('list<string>', array_column($array, 'column', null));
6973
assertType('array<string, string>', array_column($array, 'column', 'key'));
7074
assertType('array<string, array{column: string, key: string}>', array_column($array, null, 'key'));
7175
}
@@ -74,13 +78,15 @@ public function testConstantArray1(array $array): void
7478
public function testConstantArray2(array $array): void
7579
{
7680
assertType('array{}', array_column($array, 'foo'));
81+
assertType('array{}', array_column($array, 'foo', null));
7782
assertType('array{}', array_column($array, 'foo', 'key'));
7883
}
7984

8085
/** @param array{array{column: string, key: 'bar'}} $array */
8186
public function testConstantArray3(array $array): void
8287
{
8388
assertType("array{string}", array_column($array, 'column'));
89+
assertType("array{string}", array_column($array, 'column', null));
8490
assertType("array{bar: string}", array_column($array, 'column', 'key'));
8591
assertType("array{bar: array{column: string, key: 'bar'}}", array_column($array, null, 'key'));
8692
}
@@ -96,6 +102,7 @@ public function testConstantArray4(array $array): void
96102
public function testConstantArray5(array $array): void
97103
{
98104
assertType("list<'foo'>", array_column($array, 'column'));
105+
assertType("list<'foo'>", array_column($array, 'column', null));
99106
assertType("array<'bar'|int, 'foo'>", array_column($array, 'column', 'key'));
100107
assertType("array<'bar'|int, array{column?: 'foo', key?: 'bar'}>", array_column($array, null, 'key'));
101108
}
@@ -104,12 +111,14 @@ public function testConstantArray5(array $array): void
104111
public function testConstantArray6(array $array): void
105112
{
106113
assertType('list<bool|string>', array_column($array, mt_rand(0, 1) === 0 ? 'column1' : 'column2'));
114+
assertType('list<bool|string>', array_column($array, mt_rand(0, 1) === 0 ? 'column1' : 'column2', null));
107115
}
108116

109117
/** @param non-empty-array<int, array{column: string, key: string}> $array */
110118
public function testConstantArray7(array $array): void
111119
{
112120
assertType('non-empty-list<string>', array_column($array, 'column'));
121+
assertType('non-empty-list<string>', array_column($array, 'column', null));
113122
assertType('non-empty-array<string, string>', array_column($array, 'column', 'key'));
114123
assertType('non-empty-array<string, array{column: string, key: string}>', array_column($array, null, 'key'));
115124
}
@@ -142,6 +151,7 @@ public function testConstantArray11(array $array): void
142151
public function testConstantArray12(array $array): void
143152
{
144153
assertType("array{0?: 'foo'}", array_column($array, 'column'));
154+
assertType("array{0?: 'foo'}", array_column($array, 'column', null));
145155
assertType("array{bar?: 'foo'}", array_column($array, 'column', 'key'));
146156
}
147157

@@ -151,6 +161,7 @@ public function testConstantArray12(array $array): void
151161
public function testImprecise1(array $array): void
152162
{
153163
assertType("list<'foo'>", array_column($array, 'column'));
164+
assertType("list<'foo'>", array_column($array, 'column', null));
154165
assertType("array<'bar', 'foo'>", array_column($array, 'column', 'key'));
155166
assertType("array{bar: array{column?: 'foo', key: 'bar'}}", array_column($array, null, 'key'));
156167
}
@@ -166,16 +177,19 @@ public function testImprecise2(array $array): void
166177
public function testImprecise3(array $array): void
167178
{
168179
assertType('list<string>', array_column($array, 'column'));
180+
assertType('list<string>', array_column($array, 'column', null));
169181
assertType('array<int|string, string>', array_column($array, 'column', 'key'));
170182
}
171183

172184
/** @param array<int, DOMElement> $array */
173185
public function testImprecise5(array $array): void
174186
{
175187
assertType('list<string>', array_column($array, 'nodeName'));
188+
assertType('list<string>', array_column($array, 'nodeName', null));
176189
assertType('array<string, string>', array_column($array, 'nodeName', 'tagName'));
177190
assertType('array<string, DOMElement>', array_column($array, null, 'tagName'));
178191
assertType('list', array_column($array, 'foo'));
192+
assertType('list', array_column($array, 'foo', null));
179193
assertType('array<string, mixed>', array_column($array, 'foo', 'tagName'));
180194
assertType('array<string>', array_column($array, 'nodeName', 'foo'));
181195
assertType('array<DOMElement>', array_column($array, null, 'foo'));
@@ -185,9 +199,11 @@ public function testImprecise5(array $array): void
185199
public function testObjects1(array $array): void
186200
{
187201
assertType('non-empty-list<string>', array_column($array, 'nodeName'));
202+
assertType('non-empty-list<string>', array_column($array, 'nodeName', null));
188203
assertType('non-empty-array<string, string>', array_column($array, 'nodeName', 'tagName'));
189204
assertType('non-empty-array<string, DOMElement>', array_column($array, null, 'tagName'));
190205
assertType('list', array_column($array, 'foo'));
206+
assertType('list', array_column($array, 'foo', null));
191207
assertType('array<string, mixed>', array_column($array, 'foo', 'tagName'));
192208
assertType('non-empty-array<string>', array_column($array, 'nodeName', 'foo'));
193209
assertType('non-empty-array<DOMElement>', array_column($array, null, 'foo'));
@@ -197,9 +213,11 @@ public function testObjects1(array $array): void
197213
public function testObjects2(array $array): void
198214
{
199215
assertType('array{string}', array_column($array, 'nodeName'));
216+
assertType('array{string}', array_column($array, 'nodeName', null));
200217
assertType('non-empty-array<string, string>', array_column($array, 'nodeName', 'tagName'));
201218
assertType('non-empty-array<string, DOMElement>', array_column($array, null, 'tagName'));
202219
assertType('list', array_column($array, 'foo'));
220+
assertType('list', array_column($array, 'foo', null));
203221
assertType('array<string, mixed>', array_column($array, 'foo', 'tagName'));
204222
assertType('non-empty-array<string>', array_column($array, 'nodeName', 'foo'));
205223
assertType('non-empty-array<DOMElement>', array_column($array, null, 'foo'));
@@ -214,6 +232,7 @@ final class Foo
214232
public function doFoo(array $a): void
215233
{
216234
assertType('array{}', array_column($a, 'nodeName'));
235+
assertType('array{}', array_column($a, 'nodeName', null));
217236
assertType('array{}', array_column($a, 'nodeName', 'tagName'));
218237
}
219238

tests/PHPStan/Analyser/nsrt/array-column.php

+20
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ class ArrayColumnTest
1313
public function testArray1(array $array): void
1414
{
1515
assertType('list<string>', array_column($array, 'column'));
16+
assertType('list<string>', array_column($array, 'column', null));
1617
assertType('array<int|string, string>', array_column($array, 'column', 'key'));
1718
assertType('array<int|string, array<string, string>>', array_column($array, null, 'key'));
1819
}
@@ -22,12 +23,14 @@ public function testArray2(array $array): void
2223
{
2324
// Note: Array may still be empty!
2425
assertType('list<string>', array_column($array, 'column'));
26+
assertType('list<string>', array_column($array, 'column', null));
2527
}
2628

2729
/** @param array{} $array */
2830
public function testArray3(array $array): void
2931
{
3032
assertType('array{}', array_column($array, 'column'));
33+
assertType('array{}', array_column($array, 'column', null));
3134
assertType('array{}', array_column($array, 'column', 'key'));
3235
assertType('array{}', array_column($array, null, 'key'));
3336
}
@@ -66,6 +69,7 @@ public function testArray8(array $array): void
6669
public function testConstantArray1(array $array): void
6770
{
6871
assertType('list<string>', array_column($array, 'column'));
72+
assertType('list<string>', array_column($array, 'column', null));
6973
assertType('array<string, string>', array_column($array, 'column', 'key'));
7074
assertType('array<string, array{column: string, key: string}>', array_column($array, null, 'key'));
7175
}
@@ -74,13 +78,15 @@ public function testConstantArray1(array $array): void
7478
public function testConstantArray2(array $array): void
7579
{
7680
assertType('array{}', array_column($array, 'foo'));
81+
assertType('array{}', array_column($array, 'foo', null));
7782
assertType('array{}', array_column($array, 'foo', 'key'));
7883
}
7984

8085
/** @param array{array{column: string, key: 'bar'}} $array */
8186
public function testConstantArray3(array $array): void
8287
{
8388
assertType("array{string}", array_column($array, 'column'));
89+
assertType("array{string}", array_column($array, 'column', null));
8490
assertType("array{bar: string}", array_column($array, 'column', 'key'));
8591
assertType("array{bar: array{column: string, key: 'bar'}}", array_column($array, null, 'key'));
8692
}
@@ -96,6 +102,7 @@ public function testConstantArray4(array $array): void
96102
public function testConstantArray5(array $array): void
97103
{
98104
assertType("list<'foo'>", array_column($array, 'column'));
105+
assertType("list<'foo'>", array_column($array, 'column', null));
99106
assertType("array<'bar'|int, 'foo'>", array_column($array, 'column', 'key'));
100107
assertType("array<'bar'|int, array{column?: 'foo', key?: 'bar'}>", array_column($array, null, 'key'));
101108
}
@@ -104,12 +111,14 @@ public function testConstantArray5(array $array): void
104111
public function testConstantArray6(array $array): void
105112
{
106113
assertType('list<bool|string>', array_column($array, mt_rand(0, 1) === 0 ? 'column1' : 'column2'));
114+
assertType('list<bool|string>', array_column($array, mt_rand(0, 1) === 0 ? 'column1' : 'column2', null));
107115
}
108116

109117
/** @param non-empty-array<int, array{column: string, key: string}> $array */
110118
public function testConstantArray7(array $array): void
111119
{
112120
assertType('non-empty-list<string>', array_column($array, 'column'));
121+
assertType('non-empty-list<string>', array_column($array, 'column', null));
113122
assertType('non-empty-array<string, string>', array_column($array, 'column', 'key'));
114123
assertType('non-empty-array<string, array{column: string, key: string}>', array_column($array, null, 'key'));
115124
}
@@ -142,20 +151,23 @@ public function testConstantArray11(array $array): void
142151
public function testConstantArray12(array $array): void
143152
{
144153
assertType("array{0?: 'foo'}", array_column($array, 'column'));
154+
assertType("array{0?: 'foo'}", array_column($array, 'column', null));
145155
assertType("array{bar?: 'foo'}", array_column($array, 'column', 'key'));
146156
}
147157

148158
/** @param array{0?: array{column: 'foo1', key: 'bar1'}, 1?: array{column: 'foo2', key: 'bar2'}} $array */
149159
public function testConstantArray13(array $array): void
150160
{
151161
assertType("array{0?: 'foo1'|'foo2', 1?: 'foo2'}", array_column($array, 'column'));
162+
assertType("array{0?: 'foo1'|'foo2', 1?: 'foo2'}", array_column($array, 'column', null));
152163
assertType("array{bar1?: 'foo1', bar2?: 'foo2'}", array_column($array, 'column', 'key'));
153164
}
154165

155166
/** @param array{0?: array{column: 'foo1', key: 'bar1'}, 1: array{column: 'foo2', key: 'bar2'}} $array */
156167
public function testConstantArray14(array $array): void
157168
{
158169
assertType("array{0: 'foo1'|'foo2', 1?: 'foo2'}", array_column($array, 'column'));
170+
assertType("array{0: 'foo1'|'foo2', 1?: 'foo2'}", array_column($array, 'column', null));
159171
assertType("array{bar1?: 'foo1', bar2: 'foo2'}", array_column($array, 'column', 'key'));
160172
}
161173

@@ -165,6 +177,7 @@ public function testConstantArray14(array $array): void
165177
public function testImprecise1(array $array): void
166178
{
167179
assertType("list<'foo'>", array_column($array, 'column'));
180+
assertType("list<'foo'>", array_column($array, 'column', null));
168181
assertType("array<'bar', 'foo'>", array_column($array, 'column', 'key'));
169182
assertType("array{bar: array{column?: 'foo', key: 'bar'}}", array_column($array, null, 'key'));
170183
}
@@ -180,16 +193,19 @@ public function testImprecise2(array $array): void
180193
public function testImprecise3(array $array): void
181194
{
182195
assertType('list<string>', array_column($array, 'column'));
196+
assertType('list<string>', array_column($array, 'column', null));
183197
assertType('array<int|string, string>', array_column($array, 'column', 'key'));
184198
}
185199

186200
/** @param array<int, DOMElement> $array */
187201
public function testImprecise5(array $array): void
188202
{
189203
assertType('list<string>', array_column($array, 'nodeName'));
204+
assertType('list<string>', array_column($array, 'nodeName', null));
190205
assertType('array<string, string>', array_column($array, 'nodeName', 'tagName'));
191206
assertType('array<string, DOMElement>', array_column($array, null, 'tagName'));
192207
assertType('list', array_column($array, 'foo'));
208+
assertType('list', array_column($array, 'foo', null));
193209
assertType('array<string, mixed>', array_column($array, 'foo', 'tagName'));
194210
assertType('array<string>', array_column($array, 'nodeName', 'foo'));
195211
assertType('array<DOMElement>', array_column($array, null, 'foo'));
@@ -199,9 +215,11 @@ public function testImprecise5(array $array): void
199215
public function testObjects1(array $array): void
200216
{
201217
assertType('non-empty-list<string>', array_column($array, 'nodeName'));
218+
assertType('non-empty-list<string>', array_column($array, 'nodeName', null));
202219
assertType('non-empty-array<string, string>', array_column($array, 'nodeName', 'tagName'));
203220
assertType('non-empty-array<string, DOMElement>', array_column($array, null, 'tagName'));
204221
assertType('list', array_column($array, 'foo'));
222+
assertType('list', array_column($array, 'foo', null));
205223
assertType('array<string, mixed>', array_column($array, 'foo', 'tagName'));
206224
assertType('non-empty-array<string>', array_column($array, 'nodeName', 'foo'));
207225
assertType('non-empty-array<DOMElement>', array_column($array, null, 'foo'));
@@ -211,9 +229,11 @@ public function testObjects1(array $array): void
211229
public function testObjects2(array $array): void
212230
{
213231
assertType('array{string}', array_column($array, 'nodeName'));
232+
assertType('array{string}', array_column($array, 'nodeName', null));
214233
assertType('non-empty-array<string, string>', array_column($array, 'nodeName', 'tagName'));
215234
assertType('non-empty-array<string, DOMElement>', array_column($array, null, 'tagName'));
216235
assertType('list', array_column($array, 'foo'));
236+
assertType('list', array_column($array, 'foo', null));
217237
assertType('array<string, mixed>', array_column($array, 'foo', 'tagName'));
218238
assertType('non-empty-array<string>', array_column($array, 'nodeName', 'foo'));
219239
assertType('non-empty-array<DOMElement>', array_column($array, null, 'foo'));
+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug12954;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
$plop = [
8+
12 => [
9+
'name' => 'ROLE_USER',
10+
'description' => 'User role'
11+
],
12+
28 => [
13+
'name' => 'ROLE_ADMIN',
14+
'description' => 'Admin role'
15+
],
16+
43 => [
17+
'name' => 'ROLE_SUPER_ADMIN',
18+
'description' => 'SUPER Admin role'
19+
],
20+
];
21+
22+
$list = ['ROLE_USER', 'ROLE_ADMIN', 'ROLE_SUPER_ADMIN'];
23+
24+
$result = array_column($plop, 'name', null);
25+
26+
/**
27+
* @param list<string> $array
28+
*/
29+
function doSomething(array $array): void
30+
{
31+
assertType('list<string>', $array);
32+
}
33+
34+
doSomething($result);
35+
doSomething($list);
36+
37+
assertType('array{\'ROLE_USER\', \'ROLE_ADMIN\', \'ROLE_SUPER_ADMIN\'}', $result);
38+
assertType('array{\'ROLE_USER\', \'ROLE_ADMIN\', \'ROLE_SUPER_ADMIN\'}', $list);

tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php

+6
Original file line numberDiff line numberDiff line change
@@ -2084,4 +2084,10 @@ public function testBug12847(): void
20842084
]);
20852085
}
20862086

2087+
public function testBug12954(): void
2088+
{
2089+
$this->checkExplicitMixed = true;
2090+
$this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-12954.php'], []);
2091+
}
2092+
20872093
}

0 commit comments

Comments
 (0)