Skip to content

Commit 68780f3

Browse files
Fix mb_convert_encoding signature
1 parent 8a6f7e9 commit 68780f3

File tree

4 files changed

+94
-12
lines changed

4 files changed

+94
-12
lines changed

resources/functionMap.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6312,7 +6312,7 @@
63126312
'mb_check_encoding' => ['bool', 'var='=>'string|array<string>', 'encoding='=>'string'],
63136313
'mb_chr' => ['string|false', 'cp'=>'int', 'encoding='=>'string'],
63146314
'mb_convert_case' => ['string', 'sourcestring'=>'string', 'mode'=>'int', 'encoding='=>'string'],
6315-
'mb_convert_encoding' => ['string|array<int, string>|false', 'val'=>'string|array<int, string>', 'to_encoding'=>'string', 'from_encoding='=>'mixed'],
6315+
'mb_convert_encoding' => ['string|array<mixed>|false', 'val'=>'string|array<mixed>', 'to_encoding'=>'string', 'from_encoding='=>'mixed'],
63166316
'mb_convert_kana' => ['string', 'str'=>'string', 'option='=>'string', 'encoding='=>'string'],
63176317
'mb_convert_variables' => ['string|false', 'to_encoding'=>'string', 'from_encoding'=>'array|string', '&rw_vars'=>'string|array|object', '&...rw_vars='=>'string|array|object'],
63186318
'mb_decode_mimeheader' => ['string', 'string'=>'string'],

src/Type/Php/MbConvertEncodingFunctionReturnTypeExtension.php

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,23 @@
55
use PhpParser\Node\Expr\FuncCall;
66
use PHPStan\Analyser\Scope;
77
use PHPStan\Reflection\FunctionReflection;
8+
use PHPStan\Reflection\ParametersAcceptorSelector;
9+
use PHPStan\TrinaryLogic;
10+
use PHPStan\Type\Accessory\AccessoryArrayListType;
11+
use PHPStan\Type\Accessory\NonEmptyArrayType;
812
use PHPStan\Type\ArrayType;
13+
use PHPStan\Type\Constant\ConstantArrayType;
14+
use PHPStan\Type\Constant\ConstantBooleanType;
915
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
1016
use PHPStan\Type\IntegerType;
17+
use PHPStan\Type\MixedType;
18+
use PHPStan\Type\NeverType;
1119
use PHPStan\Type\StringType;
1220
use PHPStan\Type\Type;
21+
use PHPStan\Type\TypeCombinator;
22+
use PHPStan\Type\TypeTraverser;
23+
use PHPStan\Type\UnionType;
24+
use PHPStan\Type\VerbosityLevel;
1325

1426
final class MbConvertEncodingFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension
1527
{
@@ -30,16 +42,54 @@ public function getTypeFromFunctionCall(
3042
}
3143

3244
$argType = $scope->getType($functionCall->getArgs()[0]->value);
33-
$isString = $argType->isString();
34-
$isArray = $argType->isArray();
35-
$compare = $isString->compareTo($isArray);
36-
if ($compare === $isString) {
45+
46+
$initialReturnType = ParametersAcceptorSelector::selectFromArgs(
47+
$scope,
48+
$functionCall->getArgs(),
49+
$functionReflection->getVariants(),
50+
)->getReturnType();
51+
52+
$result = TypeCombinator::intersect($initialReturnType, $this->generalizeStringType($argType));
53+
if ($result instanceof NeverType) {
54+
return null;
55+
}
56+
57+
return TypeCombinator::union($result, new ConstantBooleanType(false));
58+
}
59+
60+
private function generalizeStringType(Type $type): Type
61+
{
62+
if ($type instanceof UnionType) {
63+
return $type->traverse([$this, 'generalizeStringType']);
64+
}
65+
66+
if ($type->isString()->yes()) {
3767
return new StringType();
38-
} elseif ($compare === $isArray) {
39-
return new ArrayType(new IntegerType(), new StringType());
4068
}
4169

42-
return null;
70+
$constantArrays = $type->getConstantArrays();
71+
if (count($constantArrays) > 0) {
72+
$types = [];
73+
foreach ($constantArrays as $constantArray) {
74+
$types[] = $constantArray->traverse([$this, 'generalizeStringType']);
75+
}
76+
77+
return TypeCombinator::union(...$types);
78+
}
79+
80+
if ($type->isArray()->yes()) {
81+
$newArrayType = new ArrayType($type->getIterableKeyType(), $this->generalizeStringType($type->getIterableValueType()));
82+
if ($type->isIterableAtLeastOnce()->yes()) {
83+
$newArrayType = TypeCombinator::intersect($newArrayType, new NonEmptyArrayType());
84+
}
85+
if ($type->isList()->yes()) {
86+
$newArrayType = TypeCombinator::intersect($newArrayType, new AccessoryArrayListType());
87+
}
88+
89+
return $newArrayType;
90+
}
91+
92+
return $type;
4393
}
4494

4595
}

tests/PHPStan/Analyser/nsrt/bug-3336.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
namespace Bug3336;
44

55
function (array $arr, string $str, $mixed): void {
6-
\PHPStan\Testing\assertType('array<int, string>', mb_convert_encoding($arr));
7-
\PHPStan\Testing\assertType('string', mb_convert_encoding($str));
8-
\PHPStan\Testing\assertType('array<int, string>|string|false', mb_convert_encoding($mixed));
9-
\PHPStan\Testing\assertType('array<int, string>|string|false', mb_convert_encoding());
6+
\PHPStan\Testing\assertType('array<mixed>|false', mb_convert_encoding($arr));
7+
\PHPStan\Testing\assertType('string|false', mb_convert_encoding($str));
8+
\PHPStan\Testing\assertType('array<mixed>|string|false', mb_convert_encoding($mixed));
9+
\PHPStan\Testing\assertType('array<mixed>|string|false', mb_convert_encoding());
1010
};
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
namespace MbConvertEncoding;
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|false', mb_convert_encoding($mixed, 'UTF-8'));
24+
\PHPStan\Testing\assertType('string|false', mb_convert_encoding($constantString, 'UTF-8'));
25+
\PHPStan\Testing\assertType('string|false', mb_convert_encoding($string, 'UTF-8'));
26+
\PHPStan\Testing\assertType('array|false', mb_convert_encoding($mixedArray, 'UTF-8'));
27+
\PHPStan\Testing\assertType('array{foo: string, bar: int, baz: string}|false', mb_convert_encoding($structuredArray, 'UTF-8'));
28+
\PHPStan\Testing\assertType('list<string>|false', mb_convert_encoding($stringList, 'UTF-8'));
29+
\PHPStan\Testing\assertType('list<int>|false', mb_convert_encoding($intList, 'UTF-8'));
30+
\PHPStan\Testing\assertType('array{foo: string, bar: int, baz: string}|string|false', mb_convert_encoding($union, 'UTF-8'));
31+
\PHPStan\Testing\assertType('array|string|false', mb_convert_encoding($int, 'UTF-8'));
32+
};

0 commit comments

Comments
 (0)