Skip to content

Commit 7358b7e

Browse files
Improve mb_convert_encoding return type
1 parent ef9aae2 commit 7358b7e

File tree

3 files changed

+93
-3
lines changed

3 files changed

+93
-3
lines changed

src/Type/Php/MbConvertEncodingFunctionReturnTypeExtension.php

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use PhpParser\Node\Expr\FuncCall;
66
use PHPStan\Analyser\Scope;
7+
use PHPStan\Php\PhpVersion;
78
use PHPStan\Reflection\FunctionReflection;
89
use PHPStan\Reflection\ParametersAcceptorSelector;
910
use PHPStan\Type\Accessory\AccessoryArrayListType;
@@ -17,10 +18,15 @@
1718
use PHPStan\Type\TypeCombinator;
1819
use PHPStan\Type\UnionType;
1920
use function count;
21+
use function str_contains;
2022

2123
final class MbConvertEncodingFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension
2224
{
2325

26+
public function __construct(private PhpVersion $phpVersion)
27+
{
28+
}
29+
2430
public function isFunctionSupported(FunctionReflection $functionReflection): bool
2531
{
2632
return $functionReflection->getName() === 'mb_convert_encoding';
@@ -46,7 +52,46 @@ public function getTypeFromFunctionCall(
4652

4753
$result = TypeCombinator::intersect($initialReturnType, $this->generalizeStringType($argType));
4854
if ($result instanceof NeverType) {
49-
return null;
55+
$result = $initialReturnType;
56+
}
57+
58+
if ($this->phpVersion->throwsValueErrorForInternalFunctions()) {
59+
if (!isset($functionCall->getArgs()[2])) {
60+
return TypeCombinator::remove($result, new ConstantBooleanType(false));
61+
}
62+
$fromEncodingArgType = $scope->getType($functionCall->getArgs()[2]->value);
63+
64+
$returnFalseIfCannotDetectEncoding = false;
65+
if (!$fromEncodingArgType->isArray()->no()) {
66+
$constantArrays = $fromEncodingArgType->getConstantArrays();
67+
if (count($constantArrays) > 0) {
68+
foreach ($constantArrays as $constantArray) {
69+
if (count($constantArray->getValueTypes()) > 1) {
70+
$returnFalseIfCannotDetectEncoding = true;
71+
break;
72+
}
73+
}
74+
} else {
75+
$returnFalseIfCannotDetectEncoding = true;
76+
}
77+
}
78+
if (!$returnFalseIfCannotDetectEncoding && !$fromEncodingArgType->isString()->no()) {
79+
$constantStrings = $fromEncodingArgType->getConstantStrings();
80+
if (count($constantStrings) > 0) {
81+
foreach ($constantStrings as $constantString) {
82+
if (str_contains($constantString->getValue(), ',')) {
83+
$returnFalseIfCannotDetectEncoding = true;
84+
break;
85+
}
86+
}
87+
} else {
88+
$returnFalseIfCannotDetectEncoding = true;
89+
}
90+
}
91+
92+
if (!$returnFalseIfCannotDetectEncoding) {
93+
return TypeCombinator::remove($result, new ConstantBooleanType(false));
94+
}
5095
}
5196

5297
return TypeCombinator::union($result, new ConstantBooleanType(false));

tests/PHPStan/Analyser/nsrt/mb_convert_encoding.php renamed to tests/PHPStan/Analyser/nsrt/mb-convert-encoding-php7.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
<?php
1+
<?php // lint <= 8.0
22

3-
namespace MbConvertEncoding;
3+
namespace MbConvertEncodingPHP7;
44

55
/**
66
* @param 'foo'|'bar' $constantString
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php // lint >= 8.0
2+
3+
namespace MbConvertEncodingPHP8;
4+
5+
/**
6+
* @param 'foo'|'bar' $constantString
7+
* @param array{foo: string, bar: int, baz: 'foo'} $structuredArray
8+
* @param list<string> $stringList
9+
* @param list<int> $intList
10+
* @param 'foo'|'bar'|array{foo: string, bar: int, baz: 'foo'}|bool $union
11+
*/
12+
function test_mb_convert_encoding(
13+
mixed $mixed,
14+
string $constantString,
15+
string $string,
16+
array $mixedArray,
17+
array $structuredArray,
18+
array $stringList,
19+
array $intList,
20+
string|array|bool $union,
21+
int $int,
22+
): void {
23+
\PHPStan\Testing\assertType('array|string', mb_convert_encoding($mixed, 'UTF-8'));
24+
\PHPStan\Testing\assertType('string', mb_convert_encoding($constantString, 'UTF-8'));
25+
\PHPStan\Testing\assertType('string', mb_convert_encoding($string, 'UTF-8'));
26+
\PHPStan\Testing\assertType('array', mb_convert_encoding($mixedArray, 'UTF-8'));
27+
\PHPStan\Testing\assertType('array{foo: string, bar: int, baz: string}', mb_convert_encoding($structuredArray, 'UTF-8'));
28+
\PHPStan\Testing\assertType('list<string>', mb_convert_encoding($stringList, 'UTF-8'));
29+
\PHPStan\Testing\assertType('list<int>', mb_convert_encoding($intList, 'UTF-8'));
30+
\PHPStan\Testing\assertType('array{foo: string, bar: int, baz: string}|string', mb_convert_encoding($union, 'UTF-8'));
31+
\PHPStan\Testing\assertType('array|string', mb_convert_encoding($int, 'UTF-8'));
32+
33+
\PHPStan\Testing\assertType('string', mb_convert_encoding($string, 'UTF-8', 'FOO'));
34+
\PHPStan\Testing\assertType('string|false', mb_convert_encoding($string, 'UTF-8', $string));
35+
\PHPStan\Testing\assertType('string|false', mb_convert_encoding($string, 'UTF-8', 'FOO,BAR'));
36+
\PHPStan\Testing\assertType('string', mb_convert_encoding($string, 'UTF-8', ['FOO']));
37+
\PHPStan\Testing\assertType('string|false', mb_convert_encoding($string, 'UTF-8', ['FOO', 'BAR']));
38+
\PHPStan\Testing\assertType('string', mb_convert_encoding($string, 'UTF-8', ['FOO,BAR']));
39+
\PHPStan\Testing\assertType('list<string>', mb_convert_encoding($stringList, 'UTF-8', 'FOO'));
40+
\PHPStan\Testing\assertType('list<string>|false', mb_convert_encoding($stringList, 'UTF-8', $string));
41+
\PHPStan\Testing\assertType('list<string>|false', mb_convert_encoding($stringList, 'UTF-8', 'FOO,BAR'));
42+
\PHPStan\Testing\assertType('list<string>', mb_convert_encoding($stringList, 'UTF-8', ['FOO']));
43+
\PHPStan\Testing\assertType('list<string>|false', mb_convert_encoding($stringList, 'UTF-8', ['FOO', 'BAR']));
44+
\PHPStan\Testing\assertType('list<string>', mb_convert_encoding($stringList, 'UTF-8', ['FOO,BAR']));
45+
};

0 commit comments

Comments
 (0)