Skip to content

Commit a1eec8b

Browse files
Introduce ArrayPadDynamicReturnTypeExtension
1 parent a645445 commit a1eec8b

File tree

3 files changed

+137
-0
lines changed

3 files changed

+137
-0
lines changed

conf/config.neon

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1277,6 +1277,11 @@ services:
12771277
tags:
12781278
- phpstan.broker.dynamicFunctionReturnTypeExtension
12791279

1280+
-
1281+
class: PHPStan\Type\Php\ArrayPadDynamicReturnTypeExtension
1282+
tags:
1283+
- phpstan.broker.dynamicFunctionReturnTypeExtension
1284+
12801285
-
12811286
class: PHPStan\Type\Php\ArrayPopFunctionReturnTypeExtension
12821287
tags:
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
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\Type\Accessory\AccessoryArrayListType;
9+
use PHPStan\Type\Accessory\NonEmptyArrayType;
10+
use PHPStan\Type\ArrayType;
11+
use PHPStan\Type\Constant\ConstantIntegerType;
12+
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
13+
use PHPStan\Type\IntegerType;
14+
use PHPStan\Type\Type;
15+
use PHPStan\Type\TypeCombinator;
16+
17+
class ArrayPadDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension
18+
{
19+
20+
public function isFunctionSupported(FunctionReflection $functionReflection): bool
21+
{
22+
return $functionReflection->getName() === 'array_pad';
23+
}
24+
25+
public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type
26+
{
27+
if (!isset($functionCall->getArgs()[2])) {
28+
return null;
29+
}
30+
31+
$arrayType = $scope->getType($functionCall->getArgs()[0]->value);
32+
$itemType = $scope->getType($functionCall->getArgs()[2]->value);
33+
34+
$returnType = new ArrayType(
35+
TypeCombinator::union($arrayType->getIterableKeyType(), new IntegerType()),
36+
TypeCombinator::union($arrayType->getIterableValueType(), $itemType),
37+
);
38+
39+
$lengthType = $scope->getType($functionCall->getArgs()[1]->value);
40+
if (
41+
$arrayType->isIterableAtLeastOnce()->yes()
42+
|| $lengthType->isSuperTypeOf(new ConstantIntegerType(0))->no()
43+
) {
44+
$returnType = TypeCombinator::intersect($returnType, new NonEmptyArrayType());
45+
}
46+
47+
if ($arrayType->isList()->yes()) {
48+
$returnType = TypeCombinator::intersect($returnType, new AccessoryArrayListType());
49+
}
50+
51+
return $returnType;
52+
}
53+
54+
}
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)