Skip to content

Commit 8019243

Browse files
u01jmg3ondrejmirtes
authored andcommitted
Support returning an array or a string in count_chars()
1 parent 65b60be commit 8019243

File tree

4 files changed

+107
-1
lines changed

4 files changed

+107
-1
lines changed

conf/config.neon

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1262,13 +1262,18 @@ services:
12621262
- phpstan.broker.dynamicFunctionReturnTypeExtension
12631263

12641264
-
1265-
class: PHPStan\Type\Php\ConstantHelper
1265+
class: PHPStan\Type\Php\ConstantHelper
12661266

12671267
-
12681268
class: PHPStan\Type\Php\CountFunctionReturnTypeExtension
12691269
tags:
12701270
- phpstan.broker.dynamicFunctionReturnTypeExtension
12711271

1272+
-
1273+
class: PHPStan\Type\Php\CountCharsFunctionDynamicReturnTypeExtension
1274+
tags:
1275+
- phpstan.broker.dynamicFunctionReturnTypeExtension
1276+
12721277
-
12731278
class: PHPStan\Type\Php\CountFunctionTypeSpecifyingExtension
12741279
tags:
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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\Php\PhpVersion;
8+
use PHPStan\Reflection\FunctionReflection;
9+
use PHPStan\Type\ArrayType;
10+
use PHPStan\Type\Constant\ConstantBooleanType;
11+
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
12+
use PHPStan\Type\IntegerRangeType;
13+
use PHPStan\Type\IntegerType;
14+
use PHPStan\Type\StringType;
15+
use PHPStan\Type\Type;
16+
use PHPStan\Type\TypeUtils;
17+
use PHPStan\Type\UnionType;
18+
use function count;
19+
20+
final class CountCharsFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension
21+
{
22+
23+
public function __construct(private PhpVersion $phpVersion)
24+
{
25+
}
26+
27+
public function isFunctionSupported(FunctionReflection $functionReflection): bool
28+
{
29+
return $functionReflection->getName() === 'count_chars';
30+
}
31+
32+
public function getTypeFromFunctionCall(
33+
FunctionReflection $functionReflection,
34+
FuncCall $functionCall,
35+
Scope $scope,
36+
): ?Type
37+
{
38+
if (count($functionCall->getArgs()) < 1) {
39+
return null;
40+
}
41+
42+
$modeType = $scope->getType($functionCall->getArgs()[1]->value);
43+
44+
if (IntegerRangeType::fromInterval(0, 2)->isSuperTypeOf($modeType)->yes()) {
45+
$arrayType = new ArrayType(new IntegerType(), new IntegerType());
46+
47+
return $this->phpVersion->throwsValueErrorForInternalFunctions()
48+
? $arrayType
49+
: TypeUtils::toBenevolentUnion(new UnionType([$arrayType, new ConstantBooleanType(false)]));
50+
}
51+
52+
$stringType = new StringType();
53+
54+
return $this->phpVersion->throwsValueErrorForInternalFunctions()
55+
? $stringType
56+
: TypeUtils::toBenevolentUnion(new UnionType([$stringType, new ConstantBooleanType(false)]));
57+
}
58+
59+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php // lint <= 7.4
2+
3+
namespace CountChars;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class X {
8+
const ABC = 'abcdef';
9+
10+
function doFoo(): void {
11+
assertType('array<int, int>|false', count_chars(self::ABC, 0));
12+
assertType('array<int, int>|false', count_chars(self::ABC, 1));
13+
assertType('array<int, int>|false', count_chars(self::ABC, 2));
14+
15+
assertType('string|false', count_chars(self::ABC, 3));
16+
assertType('string|false', count_chars(self::ABC, 4));
17+
18+
assertType('string|false', count_chars(self::ABC, -1));
19+
assertType('string|false', count_chars(self::ABC, 5));
20+
}
21+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php // lint >= 8.0
2+
3+
namespace CountChars;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class Y {
8+
const ABC = 'abcdef';
9+
10+
function doFoo(): void {
11+
assertType('array<int, int>', count_chars(self::ABC, 0));
12+
assertType('array<int, int>', count_chars(self::ABC, 1));
13+
assertType('array<int, int>', count_chars(self::ABC, 2));
14+
15+
assertType('string', count_chars(self::ABC, 3));
16+
assertType('string', count_chars(self::ABC, 4));
17+
18+
assertType('string', count_chars(self::ABC, -1));
19+
assertType('string', count_chars(self::ABC, 5));
20+
}
21+
}

0 commit comments

Comments
 (0)