Skip to content

Commit 3ee46e7

Browse files
authored
Cover non-empty-array in array_flip
1 parent dffd2c2 commit 3ee46e7

File tree

5 files changed

+106
-1
lines changed

5 files changed

+106
-1
lines changed

conf/config.neon

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -930,6 +930,11 @@ services:
930930
tags:
931931
- phpstan.broker.dynamicFunctionReturnTypeExtension
932932

933+
-
934+
class: PHPStan\Type\Php\ArrayFlipFunctionReturnTypeExtension
935+
tags:
936+
- phpstan.broker.dynamicFunctionReturnTypeExtension
937+
933938
-
934939
class: PHPStan\Type\Php\ArrayKeyDynamicReturnTypeExtension
935940
tags:
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Php;
4+
5+
use PhpParser\Node\Expr\FuncCall;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\Reflection\FunctionReflection;
8+
use PHPStan\Reflection\ParametersAcceptorSelector;
9+
use PHPStan\Type\Accessory\NonEmptyArrayType;
10+
use PHPStan\Type\ArrayType;
11+
use PHPStan\Type\Type;
12+
use PHPStan\Type\TypeCombinator;
13+
14+
class ArrayFlipFunctionReturnTypeExtension implements \PHPStan\Type\DynamicFunctionReturnTypeExtension
15+
{
16+
17+
public function isFunctionSupported(FunctionReflection $functionReflection): bool
18+
{
19+
return $functionReflection->getName() === 'array_flip';
20+
}
21+
22+
public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type
23+
{
24+
if (count($functionCall->args) !== 1) {
25+
return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType();
26+
}
27+
28+
$array = $functionCall->args[0]->value;
29+
$argType = $scope->getType($array);
30+
31+
if ($argType->isArray()->yes()) {
32+
$keyType = $argType->getIterableKeyType();
33+
$itemType = $argType->getIterableValueType();
34+
35+
$itemType = ArrayType::castToArrayKeyType($itemType);
36+
37+
$flippedArrayType = new ArrayType(
38+
$itemType,
39+
$keyType
40+
);
41+
42+
if ($argType->isIterableAtLeastOnce()->yes()) {
43+
$flippedArrayType = TypeCombinator::intersect($flippedArrayType, new NonEmptyArrayType());
44+
}
45+
46+
return $flippedArrayType;
47+
}
48+
49+
return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType();
50+
}
51+
52+
}

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@ public function dataFileAsserts(): iterable
264264
yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/infer-array-key.php');
265265
yield from $this->gatherAssertTypes(__DIR__ . '/data/offset-value-after-assign.php');
266266
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2112.php');
267+
yield from $this->gatherAssertTypes(__DIR__ . '/data/array-flip.php');
267268
yield from $this->gatherAssertTypes(__DIR__ . '/data/array-map-closure.php');
268269
yield from $this->gatherAssertTypes(__DIR__ . '/data/array-sum.php');
269270
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4573.php');
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
namespace ArrayFlip;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
/**
8+
* @param int[] $integerList
9+
*/
10+
function foo($integerList)
11+
{
12+
$flip = array_flip($integerList);
13+
assertType('array<int, (int|string)>', $flip);
14+
}
15+
16+
/**
17+
* @param mixed[] $list
18+
*/
19+
function foo3($list)
20+
{
21+
$flip = array_flip($list);
22+
23+
assertType('array<int|string, (int|string)>', $flip);
24+
}
25+
26+
/**
27+
* @param array<int, 1|2|3> $array
28+
*/
29+
function foo4($array)
30+
{
31+
$flip = array_flip($array);
32+
assertType('array<1|2|3, int>', $flip);
33+
}
34+
35+
36+
/**
37+
* @param array<1|2|3, string> $array
38+
*/
39+
function foo5($array)
40+
{
41+
$flip = array_flip($array);
42+
assertType('array<string, 1|2|3>', $flip);
43+
}

tests/PHPStan/Analyser/data/non-empty-array.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,9 @@ public function doFoo(
3737
/**
3838
* @param non-empty-array $array
3939
* @param non-empty-list $list
40+
* @param non-empty-array<string> $stringArray
4041
*/
41-
public function arrayFunctions($array, $list): void
42+
public function arrayFunctions($array, $list, $stringArray): void
4243
{
4344
assertType('array&nonEmpty', array_combine($array, $array));
4445
assertType('array&nonEmpty', array_combine($list, $list));
@@ -47,5 +48,8 @@ public function arrayFunctions($array, $list): void
4748
assertType('array&nonEmpty', array_merge([], $array));
4849
assertType('array&nonEmpty', array_merge($array, []));
4950
assertType('array&nonEmpty', array_merge($array, $array));
51+
52+
assertType('array<int|string, (int|string)>&nonEmpty', array_flip($array));
53+
assertType('array<string, (int|string)>&nonEmpty', array_flip($stringArray));
5054
}
5155
}

0 commit comments

Comments
 (0)