diff --git a/conf/config.neon b/conf/config.neon index e307d1380b..24156dc09b 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1262,13 +1262,18 @@ services: - phpstan.broker.dynamicFunctionReturnTypeExtension - - class: PHPStan\Type\Php\ConstantHelper + class: PHPStan\Type\Php\ConstantHelper - class: PHPStan\Type\Php\CountFunctionReturnTypeExtension tags: - phpstan.broker.dynamicFunctionReturnTypeExtension + - + class: PHPStan\Type\Php\CountCharsFunctionDynamicReturnTypeExtension + tags: + - phpstan.broker.dynamicFunctionReturnTypeExtension + - class: PHPStan\Type\Php\CountFunctionTypeSpecifyingExtension tags: diff --git a/src/Type/Php/CountCharsFunctionDynamicReturnTypeExtension.php b/src/Type/Php/CountCharsFunctionDynamicReturnTypeExtension.php new file mode 100644 index 0000000000..cddb7e7989 --- /dev/null +++ b/src/Type/Php/CountCharsFunctionDynamicReturnTypeExtension.php @@ -0,0 +1,59 @@ +getName() === 'count_chars'; + } + + public function getTypeFromFunctionCall( + FunctionReflection $functionReflection, + FuncCall $functionCall, + Scope $scope, + ): ?Type + { + if (count($functionCall->getArgs()) < 1) { + return null; + } + + $modeType = $scope->getType($functionCall->getArgs()[1]->value); + + if (IntegerRangeType::fromInterval(0, 2)->isSuperTypeOf($modeType)->yes()) { + $arrayType = new ArrayType(new IntegerType(), new IntegerType()); + + return $this->phpVersion->throwsValueErrorForInternalFunctions() + ? $arrayType + : TypeUtils::toBenevolentUnion(new UnionType([$arrayType, new ConstantBooleanType(false)])); + } + + $stringType = new StringType(); + + return $this->phpVersion->throwsValueErrorForInternalFunctions() + ? $stringType + : TypeUtils::toBenevolentUnion(new UnionType([$stringType, new ConstantBooleanType(false)])); + } + +} diff --git a/tests/PHPStan/Analyser/nsrt/count-chars-7.4.php b/tests/PHPStan/Analyser/nsrt/count-chars-7.4.php new file mode 100644 index 0000000000..713d73a5e1 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/count-chars-7.4.php @@ -0,0 +1,21 @@ +|false', count_chars(self::ABC, 0)); + assertType('array|false', count_chars(self::ABC, 1)); + assertType('array|false', count_chars(self::ABC, 2)); + + assertType('string|false', count_chars(self::ABC, 3)); + assertType('string|false', count_chars(self::ABC, 4)); + + assertType('string|false', count_chars(self::ABC, -1)); + assertType('string|false', count_chars(self::ABC, 5)); + } +} diff --git a/tests/PHPStan/Analyser/nsrt/count-chars-8.0.php b/tests/PHPStan/Analyser/nsrt/count-chars-8.0.php new file mode 100644 index 0000000000..88a165e931 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/count-chars-8.0.php @@ -0,0 +1,21 @@ += 8.0 + +namespace CountChars; + +use function PHPStan\Testing\assertType; + +class Y { + const ABC = 'abcdef'; + + function doFoo(): void { + assertType('array', count_chars(self::ABC, 0)); + assertType('array', count_chars(self::ABC, 1)); + assertType('array', count_chars(self::ABC, 2)); + + assertType('string', count_chars(self::ABC, 3)); + assertType('string', count_chars(self::ABC, 4)); + + assertType('string', count_chars(self::ABC, -1)); + assertType('string', count_chars(self::ABC, 5)); + } +}