Skip to content

Commit 66e3bd5

Browse files
VincentLangletondrejmirtes
authored andcommitted
Handle encoding for mb_str_split returnTypeExtension
1 parent 80d8afb commit 66e3bd5

4 files changed

Lines changed: 152 additions & 7 deletions

File tree

build/baseline-8.0.neon

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,8 @@ parameters:
5050
message: "#^Call to function method_exists\\(\\) with ReflectionClass and 'getAttributes' will always evaluate to true\\.$#"
5151
count: 1
5252
path: ../src/Reflection/ClassReflection.php
53+
54+
-
55+
message: "#^Strict comparison using \\=\\=\\= between array and false will always evaluate to false\\.$#"
56+
count: 1
57+
path: ../src/Type/Php/StrSplitFunctionReturnTypeExtension.php

src/Type/Php/StrSplitFunctionReturnTypeExtension.php

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,29 @@
1515
use PHPStan\Type\IntegerType;
1616
use PHPStan\Type\StringType;
1717
use PHPStan\Type\Type;
18+
use PHPStan\Type\TypeUtils;
1819

1920
final class StrSplitFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension
2021
{
2122

23+
/** @var string[] */
24+
private array $supportedEncodings;
25+
26+
public function __construct()
27+
{
28+
$supportedEncodings = [];
29+
if (function_exists('mb_list_encodings')) {
30+
foreach (mb_list_encodings() as $encoding) {
31+
$aliases = mb_encoding_aliases($encoding);
32+
if ($aliases === false) {
33+
throw new \PHPStan\ShouldNotHappenException();
34+
}
35+
$supportedEncodings = array_merge($supportedEncodings, $aliases, [$encoding]);
36+
}
37+
}
38+
$this->supportedEncodings = array_map('strtoupper', $supportedEncodings);
39+
}
40+
2241
public function isFunctionSupported(FunctionReflection $functionReflection): bool
2342
{
2443
return in_array($functionReflection->getName(), ['str_split', 'mb_str_split'], true);
@@ -32,32 +51,63 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
3251
return $defaultReturnType;
3352
}
3453

35-
$splitLength = 1;
3654
if (count($functionCall->args) >= 2) {
3755
$splitLengthType = $scope->getType($functionCall->args[1]->value);
38-
if (!$splitLengthType instanceof ConstantIntegerType) {
39-
return $defaultReturnType;
56+
if ($splitLengthType instanceof ConstantIntegerType) {
57+
$splitLength = $splitLengthType->getValue();
58+
if ($splitLength < 1) {
59+
return new ConstantBooleanType(false);
60+
}
4061
}
41-
$splitLength = $splitLengthType->getValue();
42-
if ($splitLength < 1) {
43-
return new ConstantBooleanType(false);
62+
} else {
63+
$splitLength = 1;
64+
}
65+
66+
if ($functionReflection->getName() === 'mb_str_split') {
67+
if (count($functionCall->args) >= 3) {
68+
$strings = TypeUtils::getConstantStrings($scope->getType($functionCall->args[2]->value));
69+
$values = array_unique(array_map(static function (ConstantStringType $encoding): string {
70+
return $encoding->getValue();
71+
}, $strings));
72+
73+
if (count($values) !== 1) {
74+
return $defaultReturnType;
75+
}
76+
77+
$encoding = $values[0];
78+
if (!$this->isSupportedEncoding($encoding)) {
79+
return new ConstantBooleanType(false);
80+
}
81+
} else {
82+
$encoding = mb_internal_encoding();
4483
}
4584
}
4685

86+
if (!isset($splitLength)) {
87+
return $defaultReturnType;
88+
}
89+
4790
$stringType = $scope->getType($functionCall->args[0]->value);
4891
if (!$stringType instanceof ConstantStringType) {
4992
return new ArrayType(new IntegerType(), new StringType());
5093
}
5194
$stringValue = $stringType->getValue();
5295

53-
$items = str_split($stringValue, $splitLength);
96+
$items = isset($encoding)
97+
? mb_str_split($stringValue, $splitLength, $encoding)
98+
: str_split($stringValue, $splitLength);
5499
if (!is_array($items)) {
55100
throw new \PHPStan\ShouldNotHappenException();
56101
}
57102

58103
return self::createConstantArrayFrom($items, $scope);
59104
}
60105

106+
private function isSupportedEncoding(string $encoding): bool
107+
{
108+
return in_array(strtoupper($encoding), $this->supportedEncodings, true);
109+
}
110+
61111
/**
62112
* @param string[] $constantArray
63113
* @param \PHPStan\Analyser\Scope $scope

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9592,6 +9592,78 @@ public function dataPhp74Functions(): array
95929592
PHP_VERSION_ID < 80000 ? 'array<int, string>|false' : 'array<int, string>',
95939593
'$mbStrSplitConstantStringWithVariableStringAndVariableSplitLength',
95949594
],
9595+
[
9596+
"array('a', 'b', 'c', 'd', 'e', 'f')",
9597+
'$mbStrSplitConstantStringWithOneSplitLengthAndValidEncoding',
9598+
],
9599+
[
9600+
'false',
9601+
'$mbStrSplitConstantStringWithOneSplitLengthAndInvalidEncoding',
9602+
],
9603+
[
9604+
PHP_VERSION_ID < 80000 ? 'array<int, string>|false' : 'array<int, string>',
9605+
'$mbStrSplitConstantStringWithOneSplitLengthAndVariableEncoding',
9606+
],
9607+
[
9608+
"array('abcdef')",
9609+
'$mbStrSplitConstantStringWithGreaterSplitLengthThanStringLengthAndValidEncoding',
9610+
],
9611+
[
9612+
'false',
9613+
'$mbStrSplitConstantStringWithGreaterSplitLengthThanStringLengthAndInvalidEncoding',
9614+
],
9615+
[
9616+
PHP_VERSION_ID < 80000 ? 'array<int, string>|false' : 'array<int, string>',
9617+
'$mbStrSplitConstantStringWithGreaterSplitLengthThanStringLengthAndVariableEncoding',
9618+
],
9619+
[
9620+
'false',
9621+
'$mbStrSplitConstantStringWithFailureSplitLengthAndValidEncoding',
9622+
],
9623+
[
9624+
'false',
9625+
'$mbStrSplitConstantStringWithFailureSplitLengthAndInvalidEncoding',
9626+
],
9627+
[
9628+
'false',
9629+
'$mbStrSplitConstantStringWithFailureSplitLengthAndVariableEncoding',
9630+
],
9631+
[
9632+
PHP_VERSION_ID < 80000 ? 'array<int, string>|false' : 'array<int, string>',
9633+
'$mbStrSplitConstantStringWithInvalidSplitLengthTypeAndValidEncoding',
9634+
],
9635+
[
9636+
'false',
9637+
'$mbStrSplitConstantStringWithInvalidSplitLengthTypeAndInvalidEncoding',
9638+
],
9639+
[
9640+
PHP_VERSION_ID < 80000 ? 'array<int, string>|false' : 'array<int, string>',
9641+
'$mbStrSplitConstantStringWithInvalidSplitLengthTypeAndVariableEncoding',
9642+
],
9643+
[
9644+
'array<int, string>',
9645+
'$mbStrSplitConstantStringWithVariableStringAndConstantSplitLengthAndValidEncoding',
9646+
],
9647+
[
9648+
'false',
9649+
'$mbStrSplitConstantStringWithVariableStringAndConstantSplitLengthAndInvalidEncoding',
9650+
],
9651+
[
9652+
PHP_VERSION_ID < 80000 ? 'array<int, string>|false' : 'array<int, string>',
9653+
'$mbStrSplitConstantStringWithVariableStringAndConstantSplitLengthAndVariableEncoding',
9654+
],
9655+
[
9656+
PHP_VERSION_ID < 80000 ? 'array<int, string>|false' : 'array<int, string>',
9657+
'$mbStrSplitConstantStringWithVariableStringAndVariableSplitLengthAndValidEncoding',
9658+
],
9659+
[
9660+
'false',
9661+
'$mbStrSplitConstantStringWithVariableStringAndVariableSplitLengthAndInvalidEncoding',
9662+
],
9663+
[
9664+
PHP_VERSION_ID < 80000 ? 'array<int, string>|false' : 'array<int, string>',
9665+
'$mbStrSplitConstantStringWithVariableStringAndVariableSplitLengthAndVariableEncoding',
9666+
],
95959667
];
95969668
}
95979669

tests/PHPStan/Analyser/data/php74_functions.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,22 @@
1212
$mbStrSplitConstantStringWithInvalidSplitLengthType = mb_str_split('abcdef', []);
1313
$mbStrSplitConstantStringWithVariableStringAndConstantSplitLength = mb_str_split(doFoo() ? 'abcdef' : 'ghijkl', 1);
1414
$mbStrSplitConstantStringWithVariableStringAndVariableSplitLength = mb_str_split(doFoo() ? 'abcdef' : 'ghijkl', doFoo() ? 1 : 2);
15+
$mbStrSplitConstantStringWithOneSplitLengthAndValidEncoding = mb_str_split('abcdef', 1, 'UTF-8');
16+
$mbStrSplitConstantStringWithOneSplitLengthAndInvalidEncoding = mb_str_split('abcdef', 1, 'FAKE');
17+
$mbStrSplitConstantStringWithOneSplitLengthAndVariableEncoding = mb_str_split('abcdef', 1, doFoo());
18+
$mbStrSplitConstantStringWithGreaterSplitLengthThanStringLengthAndValidEncoding = mb_str_split('abcdef', 999, 'UTF-8');
19+
$mbStrSplitConstantStringWithGreaterSplitLengthThanStringLengthAndInvalidEncoding = mb_str_split('abcdef', 999, 'FAKE');
20+
$mbStrSplitConstantStringWithGreaterSplitLengthThanStringLengthAndVariableEncoding = mb_str_split('abcdef', 999, doFoo());
21+
$mbStrSplitConstantStringWithFailureSplitLengthAndValidEncoding = mb_str_split('abcdef', 0, 'UTF-8');
22+
$mbStrSplitConstantStringWithFailureSplitLengthAndInvalidEncoding = mb_str_split('abcdef', 0, 'FAKE');
23+
$mbStrSplitConstantStringWithFailureSplitLengthAndVariableEncoding = mb_str_split('abcdef', 0, doFoo());
24+
$mbStrSplitConstantStringWithInvalidSplitLengthTypeAndValidEncoding = mb_str_split('abcdef', [], 'UTF-8');
25+
$mbStrSplitConstantStringWithInvalidSplitLengthTypeAndInvalidEncoding = mb_str_split('abcdef', [], 'FAKE');
26+
$mbStrSplitConstantStringWithInvalidSplitLengthTypeAndVariableEncoding = mb_str_split('abcdef', [], doFoo());
27+
$mbStrSplitConstantStringWithVariableStringAndConstantSplitLengthAndValidEncoding = mb_str_split(doFoo() ? 'abcdef' : 'ghijkl', 1, 'UTF-8');
28+
$mbStrSplitConstantStringWithVariableStringAndConstantSplitLengthAndInvalidEncoding = mb_str_split(doFoo() ? 'abcdef' : 'ghijkl', 1, 'FAKE');
29+
$mbStrSplitConstantStringWithVariableStringAndConstantSplitLengthAndVariableEncoding = mb_str_split(doFoo() ? 'abcdef' : 'ghijkl', 1, doFoo());
30+
$mbStrSplitConstantStringWithVariableStringAndVariableSplitLengthAndValidEncoding = mb_str_split(doFoo() ? 'abcdef' : 'ghijkl', doFoo() ? 1 : 2, 'UTF-8');
31+
$mbStrSplitConstantStringWithVariableStringAndVariableSplitLengthAndInvalidEncoding = mb_str_split(doFoo() ? 'abcdef' : 'ghijkl', doFoo() ? 1 : 2, 'FAKE');
32+
$mbStrSplitConstantStringWithVariableStringAndVariableSplitLengthAndVariableEncoding = mb_str_split(doFoo() ? 'abcdef' : 'ghijkl', doFoo() ? 1 : 2, doFoo());
1533
die;

0 commit comments

Comments
 (0)