Skip to content

Commit 4e29e4e

Browse files
committed
add array_first and array_last return type extensions
1 parent 193d801 commit 4e29e4e

File tree

3 files changed

+118
-0
lines changed

3 files changed

+118
-0
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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\DependencyInjection\AutowiredService;
8+
use PHPStan\Reflection\FunctionReflection;
9+
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
10+
use PHPStan\Type\NullType;
11+
use PHPStan\Type\Type;
12+
use PHPStan\Type\TypeCombinator;
13+
use function count;
14+
15+
#[AutowiredService]
16+
final class ArrayFirstDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension
17+
{
18+
19+
public function isFunctionSupported(FunctionReflection $functionReflection): bool
20+
{
21+
return $functionReflection->getName() === 'array_first' && $functionReflection->isBuiltin();
22+
}
23+
24+
public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type
25+
{
26+
$args = $functionCall->getArgs();
27+
28+
if (count($args) < 1) {
29+
return null;
30+
}
31+
32+
$argType = $scope->getType($args[0]->value);
33+
$iterableAtLeastOnce = $argType->isIterableAtLeastOnce();
34+
35+
if ($iterableAtLeastOnce->no()) {
36+
return new NullType();
37+
}
38+
39+
$valueType = $argType->getFirstIterableValueType();
40+
41+
if ($iterableAtLeastOnce->yes()) {
42+
return $valueType;
43+
}
44+
45+
return TypeCombinator::union($valueType, new NullType());
46+
}
47+
48+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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\DependencyInjection\AutowiredService;
8+
use PHPStan\Reflection\FunctionReflection;
9+
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
10+
use PHPStan\Type\NullType;
11+
use PHPStan\Type\Type;
12+
use PHPStan\Type\TypeCombinator;
13+
use function count;
14+
15+
#[AutowiredService]
16+
final class ArrayLastDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension
17+
{
18+
19+
public function isFunctionSupported(FunctionReflection $functionReflection): bool
20+
{
21+
return $functionReflection->getName() === 'array_last' && $functionReflection->isBuiltin();
22+
}
23+
24+
public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type
25+
{
26+
$args = $functionCall->getArgs();
27+
28+
if (count($args) < 1) {
29+
return null;
30+
}
31+
32+
$argType = $scope->getType($args[0]->value);
33+
$iterableAtLeastOnce = $argType->isIterableAtLeastOnce();
34+
35+
if ($iterableAtLeastOnce->no()) {
36+
return new NullType();
37+
}
38+
39+
$valueType = $argType->getLastIterableValueType();
40+
41+
if ($iterableAtLeastOnce->yes()) {
42+
return $valueType;
43+
}
44+
45+
return TypeCombinator::union($valueType, new NullType());
46+
}
47+
48+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php // lint >= 8.5
2+
3+
namespace ArrayFirstLast;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
/**
8+
* @param string[] $stringArray
9+
* @param non-empty-array<int, string> $nonEmptyArray
10+
*/
11+
function doFoo(array $stringArray, array $nonEmptyArray, $mixed): void
12+
{
13+
assertType("'a'", array_first([1 => 'a', 0 => 'b', 2 => 'c']));
14+
assertType('string|null', array_first($stringArray));
15+
assertType('string', array_first($nonEmptyArray));
16+
assertType('mixed', array_first($mixed));
17+
18+
assertType("'c'", array_last([1 => 'a', 0 => 'b', 2 => 'c']));
19+
assertType('string|null', array_last($stringArray));
20+
assertType('string', array_last($nonEmptyArray));
21+
assertType('mixed', array_last($mixed));
22+
}

0 commit comments

Comments
 (0)