diff --git a/src/Type/Php/StrSplitFunctionReturnTypeExtension.php b/src/Type/Php/StrSplitFunctionReturnTypeExtension.php index 34d58152dc..224c2f5a2b 100644 --- a/src/Type/Php/StrSplitFunctionReturnTypeExtension.php +++ b/src/Type/Php/StrSplitFunctionReturnTypeExtension.php @@ -9,6 +9,9 @@ use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; use PHPStan\Type\Accessory\AccessoryArrayListType; +use PHPStan\Type\Accessory\AccessoryLowercaseStringType; +use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; +use PHPStan\Type\Accessory\AccessoryUppercaseStringType; use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\ArrayType; use PHPStan\Type\Constant\ConstantArrayType; @@ -80,32 +83,43 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, } } - if (!isset($splitLength)) { - return null; - } - $stringType = $scope->getType($functionCall->getArgs()[0]->value); - - $constantStrings = $stringType->getConstantStrings(); - if (count($constantStrings) > 0) { - $results = []; - foreach ($constantStrings as $constantString) { - $items = $encoding === null - ? str_split($constantString->getValue(), $splitLength) - : @mb_str_split($constantString->getValue(), $splitLength, $encoding); - if ($items === false) { - throw new ShouldNotHappenException(); + if (isset($splitLength)) { + $constantStrings = $stringType->getConstantStrings(); + if (count($constantStrings) > 0) { + $results = []; + foreach ($constantStrings as $constantString) { + $items = $encoding === null + ? str_split($constantString->getValue(), $splitLength) + : @mb_str_split($constantString->getValue(), $splitLength, $encoding); + if ($items === false) { + throw new ShouldNotHappenException(); + } + + $results[] = self::createConstantArrayFrom($items, $scope); } - $results[] = self::createConstantArrayFrom($items, $scope); + return TypeCombinator::union(...$results); } + } + + $isInputNonEmptyString = $stringType->isNonEmptyString()->yes(); - return TypeCombinator::union(...$results); + $valueTypes = [new StringType()]; + if ($isInputNonEmptyString || $this->phpVersion->strSplitReturnsEmptyArray()) { + $valueTypes[] = new AccessoryNonEmptyStringType(); + } + if ($stringType->isLowercaseString()->yes()) { + $valueTypes[] = new AccessoryLowercaseStringType(); + } + if ($stringType->isUppercaseString()->yes()) { + $valueTypes[] = new AccessoryUppercaseStringType(); } + $returnValueType = TypeCombinator::intersect(new StringType(), ...$valueTypes); - $returnType = AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), new StringType())); + $returnType = AccessoryArrayListType::intersectWith(TypeCombinator::intersect(new ArrayType(new IntegerType(), $returnValueType))); - return $encoding === null && !$this->phpVersion->strSplitReturnsEmptyArray() + return $isInputNonEmptyString || ($encoding === null && !$this->phpVersion->strSplitReturnsEmptyArray()) ? TypeCombinator::intersect($returnType, new NonEmptyArrayType()) : $returnType; } diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 1e3afb4e70..84e12bba9e 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -29,6 +29,12 @@ private static function findTestFiles(): iterable yield $testFile; } + if (PHP_VERSION_ID >= 80200) { + yield __DIR__ . '/data/str-split-php82.php'; + } else { + yield __DIR__ . '/data/str-split.php'; + } + if (PHP_VERSION_ID < 80200 && PHP_VERSION_ID >= 80100) { yield __DIR__ . '/data/enum-reflection-php81.php'; } diff --git a/tests/PHPStan/Analyser/data/str-split-php82.php b/tests/PHPStan/Analyser/data/str-split-php82.php new file mode 100644 index 0000000000..0ef85a3e81 --- /dev/null +++ b/tests/PHPStan/Analyser/data/str-split-php82.php @@ -0,0 +1,46 @@ +', str_split($string)); + assertType('non-empty-list', str_split($nonEmptyString)); + assertType('non-empty-list', str_split($nonFalsyString)); + assertType('list', str_split($lowercaseString)); + assertType('list', str_split($uppercaseString)); + + assertType('list', str_split($string, $integer)); + assertType('non-empty-list', str_split($nonEmptyString, $integer)); + assertType('non-empty-list', str_split($nonFalsyString, $integer)); + assertType('list', str_split($lowercaseString, $integer)); + assertType('list', str_split($uppercaseString, $integer)); + + assertType('list', mb_str_split($string)); + assertType('non-empty-list', mb_str_split($nonEmptyString)); + assertType('non-empty-list', mb_str_split($nonFalsyString)); + assertType('list', mb_str_split($lowercaseString)); + assertType('list', mb_str_split($uppercaseString)); + + assertType('list', mb_str_split($string, $integer)); + assertType('non-empty-list', mb_str_split($nonEmptyString, $integer)); + assertType('non-empty-list', mb_str_split($nonFalsyString, $integer)); + assertType('list', mb_str_split($lowercaseString, $integer)); + assertType('list', mb_str_split($uppercaseString, $integer)); + } +} diff --git a/tests/PHPStan/Analyser/data/str-split.php b/tests/PHPStan/Analyser/data/str-split.php new file mode 100644 index 0000000000..78b7f36721 --- /dev/null +++ b/tests/PHPStan/Analyser/data/str-split.php @@ -0,0 +1,46 @@ +', str_split($string)); + assertType('non-empty-list', str_split($nonEmptyString)); + assertType('non-empty-list', str_split($nonFalsyString)); + assertType('non-empty-list', str_split($lowercaseString)); + assertType('non-empty-list', str_split($uppercaseString)); + + assertType('non-empty-list', str_split($string, $integer)); + assertType('non-empty-list', str_split($nonEmptyString, $integer)); + assertType('non-empty-list', str_split($nonFalsyString, $integer)); + assertType('non-empty-list', str_split($lowercaseString, $integer)); + assertType('non-empty-list', str_split($uppercaseString, $integer)); + + assertType('list', mb_str_split($string)); + assertType('non-empty-list', mb_str_split($nonEmptyString)); + assertType('non-empty-list', mb_str_split($nonFalsyString)); + assertType('list', mb_str_split($lowercaseString)); + assertType('list', mb_str_split($uppercaseString)); + + assertType('list', mb_str_split($string, $integer)); + assertType('non-empty-list', mb_str_split($nonEmptyString, $integer)); + assertType('non-empty-list', mb_str_split($nonFalsyString, $integer)); + assertType('list', mb_str_split($lowercaseString, $integer)); + assertType('list', mb_str_split($uppercaseString, $integer)); + } +}