diff --git a/src/Type/Php/MbConvertEncodingFunctionReturnTypeExtension.php b/src/Type/Php/MbConvertEncodingFunctionReturnTypeExtension.php index a09f0a823a..7878ba704e 100644 --- a/src/Type/Php/MbConvertEncodingFunctionReturnTypeExtension.php +++ b/src/Type/Php/MbConvertEncodingFunctionReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Type\Accessory\AccessoryArrayListType; @@ -17,10 +18,15 @@ use PHPStan\Type\TypeCombinator; use PHPStan\Type\UnionType; use function count; +use function str_contains; final class MbConvertEncodingFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { + public function __construct(private PhpVersion $phpVersion) + { + } + public function isFunctionSupported(FunctionReflection $functionReflection): bool { return $functionReflection->getName() === 'mb_convert_encoding'; @@ -46,7 +52,46 @@ public function getTypeFromFunctionCall( $result = TypeCombinator::intersect($initialReturnType, $this->generalizeStringType($argType)); if ($result instanceof NeverType) { - return null; + $result = $initialReturnType; + } + + if ($this->phpVersion->throwsValueErrorForInternalFunctions()) { + if (!isset($functionCall->getArgs()[2])) { + return TypeCombinator::remove($result, new ConstantBooleanType(false)); + } + $fromEncodingArgType = $scope->getType($functionCall->getArgs()[2]->value); + + $returnFalseIfCannotDetectEncoding = false; + if (!$fromEncodingArgType->isArray()->no()) { + $constantArrays = $fromEncodingArgType->getConstantArrays(); + if (count($constantArrays) > 0) { + foreach ($constantArrays as $constantArray) { + if (count($constantArray->getValueTypes()) > 1) { + $returnFalseIfCannotDetectEncoding = true; + break; + } + } + } else { + $returnFalseIfCannotDetectEncoding = true; + } + } + if (!$returnFalseIfCannotDetectEncoding && !$fromEncodingArgType->isString()->no()) { + $constantStrings = $fromEncodingArgType->getConstantStrings(); + if (count($constantStrings) > 0) { + foreach ($constantStrings as $constantString) { + if (str_contains($constantString->getValue(), ',')) { + $returnFalseIfCannotDetectEncoding = true; + break; + } + } + } else { + $returnFalseIfCannotDetectEncoding = true; + } + } + + if (!$returnFalseIfCannotDetectEncoding) { + return TypeCombinator::remove($result, new ConstantBooleanType(false)); + } } return TypeCombinator::union($result, new ConstantBooleanType(false)); diff --git a/tests/PHPStan/Analyser/nsrt/mb_convert_encoding.php b/tests/PHPStan/Analyser/nsrt/mb-convert-encoding-php7.php similarity index 96% rename from tests/PHPStan/Analyser/nsrt/mb_convert_encoding.php rename to tests/PHPStan/Analyser/nsrt/mb-convert-encoding-php7.php index f5f524d44f..d1de6c7c24 100644 --- a/tests/PHPStan/Analyser/nsrt/mb_convert_encoding.php +++ b/tests/PHPStan/Analyser/nsrt/mb-convert-encoding-php7.php @@ -1,6 +1,6 @@ -= 8.0 + +namespace MbConvertEncodingPHP8; + +/** + * @param 'foo'|'bar' $constantString + * @param array{foo: string, bar: int, baz: 'foo'} $structuredArray + * @param list $stringList + * @param list $intList + * @param 'foo'|'bar'|array{foo: string, bar: int, baz: 'foo'}|bool $union + */ +function test_mb_convert_encoding( + mixed $mixed, + string $constantString, + string $string, + array $mixedArray, + array $structuredArray, + array $stringList, + array $intList, + string|array|bool $union, + int $int, +): void { + \PHPStan\Testing\assertType('array|string', mb_convert_encoding($mixed, 'UTF-8')); + \PHPStan\Testing\assertType('string', mb_convert_encoding($constantString, 'UTF-8')); + \PHPStan\Testing\assertType('string', mb_convert_encoding($string, 'UTF-8')); + \PHPStan\Testing\assertType('array', mb_convert_encoding($mixedArray, 'UTF-8')); + \PHPStan\Testing\assertType('array{foo: string, bar: int, baz: string}', mb_convert_encoding($structuredArray, 'UTF-8')); + \PHPStan\Testing\assertType('list', mb_convert_encoding($stringList, 'UTF-8')); + \PHPStan\Testing\assertType('list', mb_convert_encoding($intList, 'UTF-8')); + \PHPStan\Testing\assertType('array{foo: string, bar: int, baz: string}|string', mb_convert_encoding($union, 'UTF-8')); + \PHPStan\Testing\assertType('array|string', mb_convert_encoding($int, 'UTF-8')); + + \PHPStan\Testing\assertType('string', mb_convert_encoding($string, 'UTF-8', 'FOO')); + \PHPStan\Testing\assertType('string|false', mb_convert_encoding($string, 'UTF-8', $string)); + \PHPStan\Testing\assertType('string|false', mb_convert_encoding($string, 'UTF-8', 'FOO,BAR')); + \PHPStan\Testing\assertType('string', mb_convert_encoding($string, 'UTF-8', ['FOO'])); + \PHPStan\Testing\assertType('string|false', mb_convert_encoding($string, 'UTF-8', ['FOO', 'BAR'])); + \PHPStan\Testing\assertType('string', mb_convert_encoding($string, 'UTF-8', ['FOO,BAR'])); + \PHPStan\Testing\assertType('list', mb_convert_encoding($stringList, 'UTF-8', 'FOO')); + \PHPStan\Testing\assertType('list|false', mb_convert_encoding($stringList, 'UTF-8', $string)); + \PHPStan\Testing\assertType('list|false', mb_convert_encoding($stringList, 'UTF-8', 'FOO,BAR')); + \PHPStan\Testing\assertType('list', mb_convert_encoding($stringList, 'UTF-8', ['FOO'])); + \PHPStan\Testing\assertType('list|false', mb_convert_encoding($stringList, 'UTF-8', ['FOO', 'BAR'])); + \PHPStan\Testing\assertType('list', mb_convert_encoding($stringList, 'UTF-8', ['FOO,BAR'])); +};