Skip to content

Commit 0b8e9cb

Browse files
Introduce ArrayPadDynamicReturnTypeExtension
1 parent 2cef2c7 commit 0b8e9cb

File tree

2 files changed

+134
-0
lines changed

2 files changed

+134
-0
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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\Accessory\AccessoryArrayListType;
10+
use PHPStan\Type\Accessory\NonEmptyArrayType;
11+
use PHPStan\Type\ArrayType;
12+
use PHPStan\Type\Constant\ConstantIntegerType;
13+
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
14+
use PHPStan\Type\IntegerType;
15+
use PHPStan\Type\Type;
16+
use PHPStan\Type\TypeCombinator;
17+
18+
#[AutowiredService]
19+
final class ArrayPadDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension
20+
{
21+
22+
public function isFunctionSupported(FunctionReflection $functionReflection): bool
23+
{
24+
return $functionReflection->getName() === 'array_pad';
25+
}
26+
27+
public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type
28+
{
29+
if (!isset($functionCall->getArgs()[2])) {
30+
return null;
31+
}
32+
33+
$arrayType = $scope->getType($functionCall->getArgs()[0]->value);
34+
$itemType = $scope->getType($functionCall->getArgs()[2]->value);
35+
36+
$returnType = new ArrayType(
37+
TypeCombinator::union($arrayType->getIterableKeyType(), new IntegerType()),
38+
TypeCombinator::union($arrayType->getIterableValueType(), $itemType),
39+
);
40+
41+
$lengthType = $scope->getType($functionCall->getArgs()[1]->value);
42+
if (
43+
$arrayType->isIterableAtLeastOnce()->yes()
44+
|| $lengthType->isSuperTypeOf(new ConstantIntegerType(0))->no()
45+
) {
46+
$returnType = TypeCombinator::intersect($returnType, new NonEmptyArrayType());
47+
}
48+
49+
if ($arrayType->isList()->yes()) {
50+
$returnType = TypeCombinator::intersect($returnType, new AccessoryArrayListType());
51+
}
52+
53+
return $returnType;
54+
}
55+
56+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
namespace ArrayPad;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class HelloWorld
8+
{
9+
/**
10+
* @param array<string, string> $arrayString
11+
* @param array<int, int> $arrayInt
12+
* @param non-empty-array<string, string> $nonEmptyArrayString
13+
* @param non-empty-array<int, int> $nonEmptyArrayInt
14+
* @param list<string> $listString
15+
* @param list<int> $listInt
16+
* @param non-empty-list<string> $nonEmptyListString
17+
* @param non-empty-list<int> $nonEmptyListInt
18+
* @param int $int
19+
* @param positive-int $positiveInt
20+
* @param negative-int $negativeInt
21+
* @param positive-int|negative-int $nonZero
22+
*/
23+
public function test(
24+
$arrayString,
25+
$arrayInt,
26+
$nonEmptyArrayString,
27+
$nonEmptyArrayInt,
28+
$listString,
29+
$listInt,
30+
$nonEmptyListString,
31+
$nonEmptyListInt,
32+
$int,
33+
$positiveInt,
34+
$negativeInt,
35+
$nonZero,
36+
): void
37+
{
38+
assertType('array<int|string, string>', array_pad($arrayString, $int, 'foo'));
39+
assertType('non-empty-array<int|string, string>', array_pad($arrayString, $positiveInt, 'foo'));
40+
assertType('non-empty-array<int|string, string>', array_pad($arrayString, $negativeInt, 'foo'));
41+
assertType('non-empty-array<int|string, string>', array_pad($arrayString, $nonZero, 'foo'));
42+
43+
assertType('array<int, \'foo\'|int>', array_pad($arrayInt, $int, 'foo'));
44+
assertType('non-empty-array<int, \'foo\'|int>', array_pad($arrayInt, $positiveInt, 'foo'));
45+
assertType('non-empty-array<int, \'foo\'|int>', array_pad($arrayInt, $negativeInt, 'foo'));
46+
assertType('non-empty-array<int, \'foo\'|int>', array_pad($arrayInt, $nonZero, 'foo'));
47+
48+
assertType('non-empty-array<int|string, string>', array_pad($nonEmptyArrayString, $int, 'foo'));
49+
assertType('non-empty-array<int|string, string>', array_pad($nonEmptyArrayString, $positiveInt, 'foo'));
50+
assertType('non-empty-array<int|string, string>', array_pad($nonEmptyArrayString, $negativeInt, 'foo'));
51+
assertType('non-empty-array<int|string, string>', array_pad($nonEmptyArrayString, $nonZero, 'foo'));
52+
53+
assertType('non-empty-array<int, \'foo\'|int>', array_pad($nonEmptyArrayInt, $int, 'foo'));
54+
assertType('non-empty-array<int, \'foo\'|int>', array_pad($nonEmptyArrayInt, $positiveInt, 'foo'));
55+
assertType('non-empty-array<int, \'foo\'|int>', array_pad($nonEmptyArrayInt, $negativeInt, 'foo'));
56+
assertType('non-empty-array<int, \'foo\'|int>', array_pad($nonEmptyArrayInt, $nonZero, 'foo'));
57+
58+
assertType('list<string>', array_pad($listString, $int, 'foo'));
59+
assertType('non-empty-list<string>', array_pad($listString, $positiveInt, 'foo'));
60+
assertType('non-empty-list<string>', array_pad($listString, $negativeInt, 'foo'));
61+
assertType('non-empty-list<string>', array_pad($listString, $nonZero, 'foo'));
62+
63+
assertType('list<\'foo\'|int>', array_pad($listInt, $int, 'foo'));
64+
assertType('non-empty-list<\'foo\'|int>', array_pad($listInt, $positiveInt, 'foo'));
65+
assertType('non-empty-list<\'foo\'|int>', array_pad($listInt, $negativeInt, 'foo'));
66+
assertType('non-empty-list<\'foo\'|int>', array_pad($listInt, $nonZero, 'foo'));
67+
68+
assertType('non-empty-list<string>', array_pad($nonEmptyListString, $int, 'foo'));
69+
assertType('non-empty-list<string>', array_pad($nonEmptyListString, $positiveInt, 'foo'));
70+
assertType('non-empty-list<string>', array_pad($nonEmptyListString, $negativeInt, 'foo'));
71+
assertType('non-empty-list<string>', array_pad($nonEmptyListString, $nonZero, 'foo'));
72+
73+
assertType('non-empty-list<\'foo\'|int>', array_pad($nonEmptyListInt, $int, 'foo'));
74+
assertType('non-empty-list<\'foo\'|int>', array_pad($nonEmptyListInt, $positiveInt, 'foo'));
75+
assertType('non-empty-list<\'foo\'|int>', array_pad($nonEmptyListInt, $negativeInt, 'foo'));
76+
assertType('non-empty-list<\'foo\'|int>', array_pad($nonEmptyListInt, $nonZero, 'foo'));
77+
}
78+
}

0 commit comments

Comments
 (0)