Skip to content

Commit 4d58c28

Browse files
herndlmondrejmirtes
authored andcommitted
Fix array_column() with explicit null $index_key
1 parent f61439f commit 4d58c28

File tree

6 files changed

+91
-7
lines changed

6 files changed

+91
-7
lines changed

src/Type/Php/ArrayColumnFunctionReturnTypeExtension.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use PHPStan\Analyser\Scope;
77
use PHPStan\Reflection\FunctionReflection;
88
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
9+
use PHPStan\Type\NullType;
910
use PHPStan\Type\Type;
1011
use function count;
1112

@@ -32,7 +33,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
3233

3334
$arrayType = $scope->getType($functionCall->getArgs()[0]->value);
3435
$columnType = $scope->getType($functionCall->getArgs()[1]->value);
35-
$indexType = $numArgs >= 3 ? $scope->getType($functionCall->getArgs()[2]->value) : null;
36+
$indexType = $numArgs >= 3 ? $scope->getType($functionCall->getArgs()[2]->value) : new NullType();
3637

3738
$constantArrayTypes = $arrayType->getConstantArrays();
3839
if (count($constantArrayTypes) === 1) {

src/Type/Php/ArrayColumnHelper.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,9 @@ public function getReturnValueType(Type $arrayType, Type $columnType, Scope $sco
5050
return [$returnValueType, $iterableAtLeastOnce];
5151
}
5252

53-
public function getReturnIndexType(Type $arrayType, ?Type $indexType, Scope $scope): Type
53+
public function getReturnIndexType(Type $arrayType, Type $indexType, Scope $scope): Type
5454
{
55-
if ($indexType !== null) {
55+
if (!$indexType->isNull()->yes()) {
5656
$iterableValueType = $arrayType->getIterableValueType();
5757

5858
$type = $this->getOffsetOrProperty($iterableValueType, $indexType, $scope, false);
@@ -69,7 +69,7 @@ public function getReturnIndexType(Type $arrayType, ?Type $indexType, Scope $sco
6969
return new IntegerType();
7070
}
7171

72-
public function handleAnyArray(Type $arrayType, Type $columnType, ?Type $indexType, Scope $scope): Type
72+
public function handleAnyArray(Type $arrayType, Type $columnType, Type $indexType, Scope $scope): Type
7373
{
7474
[$returnValueType, $iterableAtLeastOnce] = $this->getReturnValueType($arrayType, $columnType, $scope);
7575
if ($returnValueType instanceof NeverType) {
@@ -82,14 +82,14 @@ public function handleAnyArray(Type $arrayType, Type $columnType, ?Type $indexTy
8282
if ($iterableAtLeastOnce->yes()) {
8383
$returnType = TypeCombinator::intersect($returnType, new NonEmptyArrayType());
8484
}
85-
if ($indexType === null) {
85+
if ($indexType->isNull()->yes()) {
8686
$returnType = TypeCombinator::intersect($returnType, new AccessoryArrayListType());
8787
}
8888

8989
return $returnType;
9090
}
9191

92-
public function handleConstantArray(ConstantArrayType $arrayType, Type $columnType, ?Type $indexType, Scope $scope): ?Type
92+
public function handleConstantArray(ConstantArrayType $arrayType, Type $columnType, Type $indexType, Scope $scope): ?Type
9393
{
9494
$builder = ConstantArrayTypeBuilder::createEmpty();
9595

@@ -102,7 +102,7 @@ public function handleConstantArray(ConstantArrayType $arrayType, Type $columnTy
102102
continue;
103103
}
104104

105-
if ($indexType !== null) {
105+
if (!$indexType->isNull()->yes()) {
106106
$type = $this->getOffsetOrProperty($iterableValueType, $indexType, $scope, false);
107107
if ($type !== null) {
108108
$keyType = $type;

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

Lines changed: 19 additions & 0 deletions
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

Lines changed: 20 additions & 0 deletions
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'));
Lines changed: 38 additions & 0 deletions
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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2242,4 +2242,10 @@ public function testBug12847(): void
22422242
]);
22432243
}
22442244

2245+
public function testBug12954(): void
2246+
{
2247+
$this->checkExplicitMixed = true;
2248+
$this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-12954.php'], []);
2249+
}
2250+
22452251
}

0 commit comments

Comments
 (0)