Skip to content

Commit b25601f

Browse files
Fix mb_convert_encoding signature
1 parent 8a6f7e9 commit b25601f

File tree

4 files changed

+101
-12
lines changed

4 files changed

+101
-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: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,22 @@
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;
1118
use PHPStan\Type\StringType;
1219
use PHPStan\Type\Type;
20+
use PHPStan\Type\TypeCombinator;
21+
use PHPStan\Type\TypeTraverser;
22+
use PHPStan\Type\UnionType;
23+
use PHPStan\Type\VerbosityLevel;
1324

1425
final class MbConvertEncodingFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension
1526
{
@@ -30,16 +41,62 @@ public function getTypeFromFunctionCall(
3041
}
3142

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

42-
return null;
69+
$constantArrays = $type->getConstantArrays();
70+
if (count($constantArrays) > 0) {
71+
$types = [];
72+
foreach ($constantArrays as $constantArray) {
73+
$c = new ConstantArrayType(
74+
$constantArray->getKeyTypes(),
75+
array_map($this->generalizeStringType(...), $constantArray->getValueTypes()),
76+
$constantArray->getNextAutoIndexes(),
77+
$constantArray->getOptionalKeys(),
78+
$constantArray->isList(),
79+
);
80+
81+
$types[] = $c;
82+
}
83+
84+
return TypeCombinator::union(...$types);
85+
}
86+
87+
if ($type->isArray()->yes()) {
88+
$newArrayType = new ArrayType($type->getIterableKeyType(), $this->generalizeStringType($type->getIterableValueType()));
89+
if ($type->isIterableAtLeastOnce()->yes()) {
90+
$newArrayType = TypeCombinator::intersect($newArrayType, new NonEmptyArrayType());
91+
}
92+
if ($type->isList()->yes()) {
93+
$newArrayType = TypeCombinator::intersect($newArrayType, new AccessoryArrayListType());
94+
}
95+
96+
return $newArrayType;
97+
}
98+
99+
return $type;
43100
}
44101

45102
}

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<mixed>|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('TODO', mb_convert_encoding($int, 'UTF-8'));
32+
};

0 commit comments

Comments
 (0)