diff --git a/build/baseline-8.0.neon b/build/baseline-8.0.neon index 95dfa6cf8a..f4a83e799c 100644 --- a/build/baseline-8.0.neon +++ b/build/baseline-8.0.neon @@ -25,11 +25,6 @@ parameters: count: 1 path: ../src/Type/Php/StrSplitFunctionReturnTypeExtension.php - - - message: "#^Strict comparison using \\=\\=\\= between list and false will always evaluate to false\\.$#" - count: 1 - path: ../src/Type/Php/StrSplitFunctionReturnTypeExtension.php - - message: "#^Call to function is_bool\\(\\) with string will always evaluate to false\\.$#" count: 1 diff --git a/src/Php/PhpVersion.php b/src/Php/PhpVersion.php index b275c464eb..ceb8345df5 100644 --- a/src/Php/PhpVersion.php +++ b/src/Php/PhpVersion.php @@ -236,7 +236,7 @@ public function deprecatesDynamicProperties(): bool return $this->versionId >= 80200; } - public function strSplitReturnsEmptyArray(): bool + public function strSplitReturnsEmptyArrayOnEmptyString(): bool { return $this->versionId >= 80200; } diff --git a/src/Type/Php/StrSplitFunctionReturnTypeExtension.php b/src/Type/Php/StrSplitFunctionReturnTypeExtension.php index 34d58152dc..a3d43371dc 100644 --- a/src/Type/Php/StrSplitFunctionReturnTypeExtension.php +++ b/src/Type/Php/StrSplitFunctionReturnTypeExtension.php @@ -9,6 +9,7 @@ use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; use PHPStan\Type\Accessory\AccessoryArrayListType; +use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\ArrayType; use PHPStan\Type\Constant\ConstantArrayType; @@ -16,7 +17,9 @@ use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; +use PHPStan\Type\IntegerRangeType; use PHPStan\Type\IntegerType; +use PHPStan\Type\NeverType; use PHPStan\Type\StringType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; @@ -51,14 +54,15 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, if (count($functionCall->getArgs()) >= 2) { $splitLengthType = $scope->getType($functionCall->getArgs()[1]->value); - if ($splitLengthType instanceof ConstantIntegerType) { - $splitLength = $splitLengthType->getValue(); - if ($splitLength < 1) { - return new ConstantBooleanType(false); - } - } } else { - $splitLength = 1; + $splitLengthType = new ConstantIntegerType(1); + } + + if ($splitLengthType instanceof ConstantIntegerType) { + $splitLength = $splitLengthType->getValue(); + if ($splitLength < 1) { + return $this->phpVersion->throwsValueErrorForInternalFunctions() ? new NeverType() : new ConstantBooleanType(false); + } } $encoding = null; @@ -67,47 +71,70 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, $strings = $scope->getType($functionCall->getArgs()[2]->value)->getConstantStrings(); $values = array_unique(array_map(static fn (ConstantStringType $encoding): string => $encoding->getValue(), $strings)); - if (count($values) !== 1) { - return null; - } - - $encoding = $values[0]; - if (!$this->isSupportedEncoding($encoding)) { - return new ConstantBooleanType(false); + if (count($values) === 1) { + $encoding = $values[0]; + if (!$this->isSupportedEncoding($encoding)) { + return $this->phpVersion->throwsValueErrorForInternalFunctions() ? new NeverType() : new ConstantBooleanType(false); + } } } else { $encoding = mb_internal_encoding(); } } - 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) + && ($functionReflection->getName() === 'str_split' || $encoding !== null) + ) { + $constantStrings = $stringType->getConstantStrings(); + if (count($constantStrings) > 0) { + $results = []; + foreach ($constantStrings as $constantString) { + $value = $constantString->getValue(); + + if ($encoding === null && $value === '') { + // Simulate the str_split call with the analysed PHP Version instead of the runtime one. + $items = $this->phpVersion->strSplitReturnsEmptyArrayOnEmptyString() ? [] : ['']; + } else { + $items = $encoding === null + ? str_split($value, $splitLength) + : @mb_str_split($value, $splitLength, $encoding); + } + + $results[] = self::createConstantArrayFrom($items, $scope); } - $results[] = self::createConstantArrayFrom($items, $scope); + return TypeCombinator::union(...$results); } + } - return TypeCombinator::union(...$results); + $isInputNonEmptyString = $stringType->isNonEmptyString()->yes(); + + if ($isInputNonEmptyString || $this->phpVersion->strSplitReturnsEmptyArrayOnEmptyString()) { + $returnValueType = TypeCombinator::intersect(new StringType(), new AccessoryNonEmptyStringType()); + } else { + $returnValueType = new StringType(); } - $returnType = AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), new StringType())); + $returnType = AccessoryArrayListType::intersectWith(TypeCombinator::intersect(new ArrayType(new IntegerType(), $returnValueType))); + if ( + // Non-empty-string will return an array with at least an element + $isInputNonEmptyString + // str_split('', 1) returns [''] on old PHP version and [] on new ones + || ($functionReflection->getName() === 'str_split' && !$this->phpVersion->strSplitReturnsEmptyArrayOnEmptyString()) + ) { + $returnType = TypeCombinator::intersect($returnType, new NonEmptyArrayType()); + } + if ( + // Length parameter accepts int<1, max> or throws a ValueError/return false based on PHP Version. + !$this->phpVersion->throwsValueErrorForInternalFunctions() + && !IntegerRangeType::fromInterval(1, null)->isSuperTypeOf($splitLengthType)->yes() + ) { + $returnType = TypeCombinator::union($returnType, new ConstantBooleanType(false)); + } - return $encoding === null && !$this->phpVersion->strSplitReturnsEmptyArray() - ? TypeCombinator::intersect($returnType, new NonEmptyArrayType()) - : $returnType; + return $returnType; } /** diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 5facb9795c..eaf05ff43f 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -56,7 +56,9 @@ private static function findTestFiles(): iterable } else { yield __DIR__ . '/data/str-split-php74.php'; } - if (PHP_VERSION_ID >= 80000) { + if (PHP_VERSION_ID >= 80200) { + yield __DIR__ . '/data/mb-str-split-php82.php'; + } elseif (PHP_VERSION_ID >= 80000) { yield __DIR__ . '/data/mb-str-split-php80.php'; } elseif (PHP_VERSION_ID >= 74000) { yield __DIR__ . '/data/mb-str-split-php74.php'; diff --git a/tests/PHPStan/Analyser/data/mb-str-split-php80.php b/tests/PHPStan/Analyser/data/mb-str-split-php80.php index 418c3bfb5c..9f5d0def9c 100644 --- a/tests/PHPStan/Analyser/data/mb-str-split-php80.php +++ b/tests/PHPStan/Analyser/data/mb-str-split-php80.php @@ -26,69 +26,90 @@ public function legacyTest(): void assertType('array{\'abcdef\'}', $mbStrSplitConstantStringWithGreaterSplitLengthThanStringLength); $mbStrSplitConstantStringWithFailureSplitLength = mb_str_split('abcdef', 0); - assertType('false', $mbStrSplitConstantStringWithFailureSplitLength); + assertType('*NEVER*', $mbStrSplitConstantStringWithFailureSplitLength); $mbStrSplitConstantStringWithInvalidSplitLengthType = mb_str_split('abcdef', []); - assertType('list', $mbStrSplitConstantStringWithInvalidSplitLengthType); + assertType('non-empty-list', $mbStrSplitConstantStringWithInvalidSplitLengthType); $mbStrSplitConstantStringWithVariableStringAndConstantSplitLength = mb_str_split(doFoo() ? 'abcdef' : 'ghijkl', 1); assertType("array{'a', 'b', 'c', 'd', 'e', 'f'}|array{'g', 'h', 'i', 'j', 'k', 'l'}", $mbStrSplitConstantStringWithVariableStringAndConstantSplitLength); $mbStrSplitConstantStringWithVariableStringAndVariableSplitLength = mb_str_split(doFoo() ? 'abcdef' : 'ghijkl', doFoo() ? 1 : 2); - assertType('list', $mbStrSplitConstantStringWithVariableStringAndVariableSplitLength); + assertType('non-empty-list', $mbStrSplitConstantStringWithVariableStringAndVariableSplitLength); $mbStrSplitConstantStringWithOneSplitLengthAndValidEncoding = mb_str_split('abcdef', 1, 'UTF-8'); assertType("array{'a', 'b', 'c', 'd', 'e', 'f'}", $mbStrSplitConstantStringWithOneSplitLengthAndValidEncoding); $mbStrSplitConstantStringWithOneSplitLengthAndInvalidEncoding = mb_str_split('abcdef', 1, 'FAKE'); - assertType('false', $mbStrSplitConstantStringWithOneSplitLengthAndInvalidEncoding); + assertType('*NEVER*', $mbStrSplitConstantStringWithOneSplitLengthAndInvalidEncoding); $mbStrSplitConstantStringWithOneSplitLengthAndVariableEncoding = mb_str_split('abcdef', 1, doFoo()); - assertType('list', $mbStrSplitConstantStringWithOneSplitLengthAndVariableEncoding); + assertType('non-empty-list', $mbStrSplitConstantStringWithOneSplitLengthAndVariableEncoding); $mbStrSplitConstantStringWithGreaterSplitLengthThanStringLengthAndValidEncoding = mb_str_split('abcdef', 999, 'UTF-8'); assertType("array{'abcdef'}", $mbStrSplitConstantStringWithGreaterSplitLengthThanStringLengthAndValidEncoding); $mbStrSplitConstantStringWithGreaterSplitLengthThanStringLengthAndInvalidEncoding = mb_str_split('abcdef', 999, 'FAKE'); - assertType('false', $mbStrSplitConstantStringWithGreaterSplitLengthThanStringLengthAndInvalidEncoding); + assertType('*NEVER*', $mbStrSplitConstantStringWithGreaterSplitLengthThanStringLengthAndInvalidEncoding); $mbStrSplitConstantStringWithGreaterSplitLengthThanStringLengthAndVariableEncoding = mb_str_split('abcdef', 999, doFoo()); - assertType('list', $mbStrSplitConstantStringWithGreaterSplitLengthThanStringLengthAndVariableEncoding); + assertType('non-empty-list', $mbStrSplitConstantStringWithGreaterSplitLengthThanStringLengthAndVariableEncoding); $mbStrSplitConstantStringWithFailureSplitLengthAndValidEncoding = mb_str_split('abcdef', 0, 'UTF-8'); - assertType('false', $mbStrSplitConstantStringWithFailureSplitLengthAndValidEncoding); + assertType('*NEVER*', $mbStrSplitConstantStringWithFailureSplitLengthAndValidEncoding); $mbStrSplitConstantStringWithFailureSplitLengthAndInvalidEncoding = mb_str_split('abcdef', 0, 'FAKE'); - assertType('false', $mbStrSplitConstantStringWithFailureSplitLengthAndInvalidEncoding); + assertType('*NEVER*', $mbStrSplitConstantStringWithFailureSplitLengthAndInvalidEncoding); $mbStrSplitConstantStringWithFailureSplitLengthAndVariableEncoding = mb_str_split('abcdef', 0, doFoo()); - assertType('false', $mbStrSplitConstantStringWithFailureSplitLengthAndVariableEncoding); + assertType('*NEVER*', $mbStrSplitConstantStringWithFailureSplitLengthAndVariableEncoding); $mbStrSplitConstantStringWithInvalidSplitLengthTypeAndValidEncoding = mb_str_split('abcdef', [], 'UTF-8'); - assertType('list', $mbStrSplitConstantStringWithInvalidSplitLengthTypeAndValidEncoding); + assertType('non-empty-list', $mbStrSplitConstantStringWithInvalidSplitLengthTypeAndValidEncoding); $mbStrSplitConstantStringWithInvalidSplitLengthTypeAndInvalidEncoding = mb_str_split('abcdef', [], 'FAKE'); - assertType('false', $mbStrSplitConstantStringWithInvalidSplitLengthTypeAndInvalidEncoding); + assertType('*NEVER*', $mbStrSplitConstantStringWithInvalidSplitLengthTypeAndInvalidEncoding); $mbStrSplitConstantStringWithInvalidSplitLengthTypeAndVariableEncoding = mb_str_split('abcdef', [], doFoo()); - assertType('list', $mbStrSplitConstantStringWithInvalidSplitLengthTypeAndVariableEncoding); + assertType('non-empty-list', $mbStrSplitConstantStringWithInvalidSplitLengthTypeAndVariableEncoding); $mbStrSplitConstantStringWithVariableStringAndConstantSplitLengthAndValidEncoding = mb_str_split(doFoo() ? 'abcdef' : 'ghijkl', 1, 'UTF-8'); assertType("array{'a', 'b', 'c', 'd', 'e', 'f'}|array{'g', 'h', 'i', 'j', 'k', 'l'}", $mbStrSplitConstantStringWithVariableStringAndConstantSplitLengthAndValidEncoding); $mbStrSplitConstantStringWithVariableStringAndConstantSplitLengthAndInvalidEncoding = mb_str_split(doFoo() ? 'abcdef' : 'ghijkl', 1, 'FAKE'); - assertType('false', $mbStrSplitConstantStringWithVariableStringAndConstantSplitLengthAndInvalidEncoding); + assertType('*NEVER*', $mbStrSplitConstantStringWithVariableStringAndConstantSplitLengthAndInvalidEncoding); $mbStrSplitConstantStringWithVariableStringAndConstantSplitLengthAndVariableEncoding = mb_str_split(doFoo() ? 'abcdef' : 'ghijkl', 1, doFoo()); - assertType('list', $mbStrSplitConstantStringWithVariableStringAndConstantSplitLengthAndVariableEncoding); + assertType('non-empty-list', $mbStrSplitConstantStringWithVariableStringAndConstantSplitLengthAndVariableEncoding); $mbStrSplitConstantStringWithVariableStringAndVariableSplitLengthAndValidEncoding = mb_str_split(doFoo() ? 'abcdef' : 'ghijkl', doFoo() ? 1 : 2, 'UTF-8'); - assertType('list', $mbStrSplitConstantStringWithVariableStringAndVariableSplitLengthAndValidEncoding); + assertType('non-empty-list', $mbStrSplitConstantStringWithVariableStringAndVariableSplitLengthAndValidEncoding); $mbStrSplitConstantStringWithVariableStringAndVariableSplitLengthAndInvalidEncoding = mb_str_split(doFoo() ? 'abcdef' : 'ghijkl', doFoo() ? 1 : 2, 'FAKE'); - assertType('false', $mbStrSplitConstantStringWithVariableStringAndVariableSplitLengthAndInvalidEncoding); + assertType('*NEVER*', $mbStrSplitConstantStringWithVariableStringAndVariableSplitLengthAndInvalidEncoding); $mbStrSplitConstantStringWithVariableStringAndVariableSplitLengthAndVariableEncoding = mb_str_split(doFoo() ? 'abcdef' : 'ghijkl', doFoo() ? 1 : 2, doFoo()); - assertType('list', $mbStrSplitConstantStringWithVariableStringAndVariableSplitLengthAndVariableEncoding); + assertType('non-empty-list', $mbStrSplitConstantStringWithVariableStringAndVariableSplitLengthAndVariableEncoding); + } + + /** + * @param non-empty-string $nonEmptyString + * @param non-falsy-string $nonFalsyString + */ + function doFoo( + string $string, + string $nonEmptyString, + string $nonFalsyString, + string $lowercaseString, + string $uppercaseString, + int $integer, + ):void { + 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($string, $integer)); + assertType('non-empty-list', mb_str_split($nonEmptyString, $integer)); + assertType('non-empty-list', mb_str_split($nonFalsyString, $integer)); } } diff --git a/tests/PHPStan/Analyser/data/mb-str-split-php82.php b/tests/PHPStan/Analyser/data/mb-str-split-php82.php new file mode 100644 index 0000000000..0f905fe37a --- /dev/null +++ b/tests/PHPStan/Analyser/data/mb-str-split-php82.php @@ -0,0 +1,113 @@ +', $mbStrSplitConstantStringWithoutDefinedParameters); + + $mbStrSplitConstantStringWithoutDefinedSplitLength = mb_str_split('abcdef'); + assertType('array{\'a\', \'b\', \'c\', \'d\', \'e\', \'f\'}', $mbStrSplitConstantStringWithoutDefinedSplitLength); + + $mbStrSplitStringWithoutDefinedSplitLength = mb_str_split($string); + assertType('list', $mbStrSplitStringWithoutDefinedSplitLength); + + $mbStrSplitConstantStringWithOneSplitLength = mb_str_split('abcdef', 1); + assertType('array{\'a\', \'b\', \'c\', \'d\', \'e\', \'f\'}', $mbStrSplitConstantStringWithOneSplitLength); + + $mbStrSplitConstantStringWithGreaterSplitLengthThanStringLength = mb_str_split('abcdef', 999); + assertType('array{\'abcdef\'}', $mbStrSplitConstantStringWithGreaterSplitLengthThanStringLength); + + $mbStrSplitConstantStringWithFailureSplitLength = mb_str_split('abcdef', 0); + assertType('*NEVER*', $mbStrSplitConstantStringWithFailureSplitLength); + + $mbStrSplitConstantStringWithInvalidSplitLengthType = mb_str_split('abcdef', []); + assertType('non-empty-list', $mbStrSplitConstantStringWithInvalidSplitLengthType); + + $mbStrSplitConstantStringWithVariableStringAndConstantSplitLength = mb_str_split(doFoo() ? 'abcdef' : 'ghijkl', 1); + assertType("array{'a', 'b', 'c', 'd', 'e', 'f'}|array{'g', 'h', 'i', 'j', 'k', 'l'}", $mbStrSplitConstantStringWithVariableStringAndConstantSplitLength); + + $mbStrSplitConstantStringWithVariableStringAndVariableSplitLength = mb_str_split(doFoo() ? 'abcdef' : 'ghijkl', doFoo() ? 1 : 2); + assertType('non-empty-list', $mbStrSplitConstantStringWithVariableStringAndVariableSplitLength); + + $mbStrSplitConstantStringWithOneSplitLengthAndValidEncoding = mb_str_split('abcdef', 1, 'UTF-8'); + assertType("array{'a', 'b', 'c', 'd', 'e', 'f'}", $mbStrSplitConstantStringWithOneSplitLengthAndValidEncoding); + + $mbStrSplitConstantStringWithOneSplitLengthAndInvalidEncoding = mb_str_split('abcdef', 1, 'FAKE'); + assertType('*NEVER*', $mbStrSplitConstantStringWithOneSplitLengthAndInvalidEncoding); + + $mbStrSplitConstantStringWithOneSplitLengthAndVariableEncoding = mb_str_split('abcdef', 1, doFoo()); + assertType('non-empty-list', $mbStrSplitConstantStringWithOneSplitLengthAndVariableEncoding); + + $mbStrSplitConstantStringWithGreaterSplitLengthThanStringLengthAndValidEncoding = mb_str_split('abcdef', 999, 'UTF-8'); + assertType("array{'abcdef'}", $mbStrSplitConstantStringWithGreaterSplitLengthThanStringLengthAndValidEncoding); + + $mbStrSplitConstantStringWithGreaterSplitLengthThanStringLengthAndInvalidEncoding = mb_str_split('abcdef', 999, 'FAKE'); + assertType('*NEVER*', $mbStrSplitConstantStringWithGreaterSplitLengthThanStringLengthAndInvalidEncoding); + + $mbStrSplitConstantStringWithGreaterSplitLengthThanStringLengthAndVariableEncoding = mb_str_split('abcdef', 999, doFoo()); + assertType('non-empty-list', $mbStrSplitConstantStringWithGreaterSplitLengthThanStringLengthAndVariableEncoding); + + $mbStrSplitConstantStringWithFailureSplitLengthAndValidEncoding = mb_str_split('abcdef', 0, 'UTF-8'); + assertType('*NEVER*', $mbStrSplitConstantStringWithFailureSplitLengthAndValidEncoding); + + $mbStrSplitConstantStringWithFailureSplitLengthAndInvalidEncoding = mb_str_split('abcdef', 0, 'FAKE'); + assertType('*NEVER*', $mbStrSplitConstantStringWithFailureSplitLengthAndInvalidEncoding); + + $mbStrSplitConstantStringWithFailureSplitLengthAndVariableEncoding = mb_str_split('abcdef', 0, doFoo()); + assertType('*NEVER*', $mbStrSplitConstantStringWithFailureSplitLengthAndVariableEncoding); + + $mbStrSplitConstantStringWithInvalidSplitLengthTypeAndValidEncoding = mb_str_split('abcdef', [], 'UTF-8'); + assertType('non-empty-list', $mbStrSplitConstantStringWithInvalidSplitLengthTypeAndValidEncoding); + + $mbStrSplitConstantStringWithInvalidSplitLengthTypeAndInvalidEncoding = mb_str_split('abcdef', [], 'FAKE'); + assertType('*NEVER*', $mbStrSplitConstantStringWithInvalidSplitLengthTypeAndInvalidEncoding); + + $mbStrSplitConstantStringWithInvalidSplitLengthTypeAndVariableEncoding = mb_str_split('abcdef', [], doFoo()); + assertType('non-empty-list', $mbStrSplitConstantStringWithInvalidSplitLengthTypeAndVariableEncoding); + + $mbStrSplitConstantStringWithVariableStringAndConstantSplitLengthAndValidEncoding = mb_str_split(doFoo() ? 'abcdef' : 'ghijkl', 1, 'UTF-8'); + assertType("array{'a', 'b', 'c', 'd', 'e', 'f'}|array{'g', 'h', 'i', 'j', 'k', 'l'}", $mbStrSplitConstantStringWithVariableStringAndConstantSplitLengthAndValidEncoding); + + $mbStrSplitConstantStringWithVariableStringAndConstantSplitLengthAndInvalidEncoding = mb_str_split(doFoo() ? 'abcdef' : 'ghijkl', 1, 'FAKE'); + assertType('*NEVER*', $mbStrSplitConstantStringWithVariableStringAndConstantSplitLengthAndInvalidEncoding); + + $mbStrSplitConstantStringWithVariableStringAndConstantSplitLengthAndVariableEncoding = mb_str_split(doFoo() ? 'abcdef' : 'ghijkl', 1, doFoo()); + assertType('non-empty-list', $mbStrSplitConstantStringWithVariableStringAndConstantSplitLengthAndVariableEncoding); + + $mbStrSplitConstantStringWithVariableStringAndVariableSplitLengthAndValidEncoding = mb_str_split(doFoo() ? 'abcdef' : 'ghijkl', doFoo() ? 1 : 2, 'UTF-8'); + assertType('non-empty-list', $mbStrSplitConstantStringWithVariableStringAndVariableSplitLengthAndValidEncoding); + + $mbStrSplitConstantStringWithVariableStringAndVariableSplitLengthAndInvalidEncoding = mb_str_split(doFoo() ? 'abcdef' : 'ghijkl', doFoo() ? 1 : 2, 'FAKE'); + assertType('*NEVER*', $mbStrSplitConstantStringWithVariableStringAndVariableSplitLengthAndInvalidEncoding); + + $mbStrSplitConstantStringWithVariableStringAndVariableSplitLengthAndVariableEncoding = mb_str_split(doFoo() ? 'abcdef' : 'ghijkl', doFoo() ? 1 : 2, doFoo()); + assertType('non-empty-list', $mbStrSplitConstantStringWithVariableStringAndVariableSplitLengthAndVariableEncoding); + } + + /** + * @param non-empty-string $nonEmptyString + * @param non-falsy-string $nonFalsyString + */ + function doFoo( + string $string, + string $nonEmptyString, + string $nonFalsyString, + int $integer, + ):void { + 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($string, $integer)); + assertType('non-empty-list', mb_str_split($nonEmptyString, $integer)); + assertType('non-empty-list', mb_str_split($nonFalsyString, $integer)); + } +} diff --git a/tests/PHPStan/Analyser/data/str-split-php74.php b/tests/PHPStan/Analyser/data/str-split-php74.php index 972f247a7a..da1c955fb0 100644 --- a/tests/PHPStan/Analyser/data/str-split-php74.php +++ b/tests/PHPStan/Analyser/data/str-split-php74.php @@ -28,13 +28,13 @@ public function legacyTest() { assertType('false', $strSplitConstantStringWithFailureSplitLength); $strSplitConstantStringWithInvalidSplitLengthType = str_split('abcdef', []); - assertType('non-empty-list|false', $strSplitConstantStringWithInvalidSplitLengthType); + assertType('non-empty-list|false', $strSplitConstantStringWithInvalidSplitLengthType); $strSplitConstantStringWithVariableStringAndConstantSplitLength = str_split(doFoo() ? 'abcdef' : 'ghijkl', 1); assertType("array{'a', 'b', 'c', 'd', 'e', 'f'}|array{'g', 'h', 'i', 'j', 'k', 'l'}", $strSplitConstantStringWithVariableStringAndConstantSplitLength); $strSplitConstantStringWithVariableStringAndVariableSplitLength = str_split(doFoo() ? 'abcdef' : 'ghijkl', doFoo() ? 1 : 2); - assertType('non-empty-list|false', $strSplitConstantStringWithVariableStringAndVariableSplitLength); + assertType('non-empty-list', $strSplitConstantStringWithVariableStringAndVariableSplitLength); } } diff --git a/tests/PHPStan/Analyser/data/str-split-php80.php b/tests/PHPStan/Analyser/data/str-split-php80.php index 1cf04a0cfd..7fe3a36ef9 100644 --- a/tests/PHPStan/Analyser/data/str-split-php80.php +++ b/tests/PHPStan/Analyser/data/str-split-php80.php @@ -25,16 +25,35 @@ public function legacyTest() { assertType('array{\'abcdef\'}', $strSplitConstantStringWithGreaterSplitLengthThanStringLength); $strSplitConstantStringWithFailureSplitLength = str_split('abcdef', 0); - assertType('false', $strSplitConstantStringWithFailureSplitLength); + assertType('*NEVER*', $strSplitConstantStringWithFailureSplitLength); $strSplitConstantStringWithInvalidSplitLengthType = str_split('abcdef', []); - assertType('non-empty-list', $strSplitConstantStringWithInvalidSplitLengthType); + assertType('non-empty-list', $strSplitConstantStringWithInvalidSplitLengthType); $strSplitConstantStringWithVariableStringAndConstantSplitLength = str_split(doFoo() ? 'abcdef' : 'ghijkl', 1); assertType("array{'a', 'b', 'c', 'd', 'e', 'f'}|array{'g', 'h', 'i', 'j', 'k', 'l'}", $strSplitConstantStringWithVariableStringAndConstantSplitLength); $strSplitConstantStringWithVariableStringAndVariableSplitLength = str_split(doFoo() ? 'abcdef' : 'ghijkl', doFoo() ? 1 : 2); - assertType('non-empty-list', $strSplitConstantStringWithVariableStringAndVariableSplitLength); + assertType('non-empty-list', $strSplitConstantStringWithVariableStringAndVariableSplitLength); } + + /** + * @param non-empty-string $nonEmptyString + * @param non-falsy-string $nonFalsyString + */ + function doFoo( + string $string, + string $nonEmptyString, + string $nonFalsyString, + int $integer, + ):void { + assertType('non-empty-list', str_split($string)); + assertType('non-empty-list', str_split($nonEmptyString)); + assertType('non-empty-list', str_split($nonFalsyString)); + + assertType('non-empty-list', str_split($string, $integer)); + assertType('non-empty-list', str_split($nonEmptyString, $integer)); + assertType('non-empty-list', str_split($nonFalsyString, $integer)); + } } diff --git a/tests/PHPStan/Analyser/data/str-split-php82.php b/tests/PHPStan/Analyser/data/str-split-php82.php index 8d1e00d59f..22720747e6 100644 --- a/tests/PHPStan/Analyser/data/str-split-php82.php +++ b/tests/PHPStan/Analyser/data/str-split-php82.php @@ -16,7 +16,7 @@ public function legacyTest() { assertType('array{\'a\', \'b\', \'c\', \'d\', \'e\', \'f\'}', $strSplitConstantStringWithoutDefinedSplitLength); $strSplitStringWithoutDefinedSplitLength = str_split($string); - assertType('list', $strSplitStringWithoutDefinedSplitLength); + assertType('list', $strSplitStringWithoutDefinedSplitLength); $strSplitConstantStringWithOneSplitLength = str_split('abcdef', 1); assertType('array{\'a\', \'b\', \'c\', \'d\', \'e\', \'f\'}', $strSplitConstantStringWithOneSplitLength); @@ -25,16 +25,35 @@ public function legacyTest() { assertType('array{\'abcdef\'}', $strSplitConstantStringWithGreaterSplitLengthThanStringLength); $strSplitConstantStringWithFailureSplitLength = str_split('abcdef', 0); - assertType('false', $strSplitConstantStringWithFailureSplitLength); + assertType('*NEVER*', $strSplitConstantStringWithFailureSplitLength); $strSplitConstantStringWithInvalidSplitLengthType = str_split('abcdef', []); - assertType('list', $strSplitConstantStringWithInvalidSplitLengthType); + assertType('non-empty-list', $strSplitConstantStringWithInvalidSplitLengthType); $strSplitConstantStringWithVariableStringAndConstantSplitLength = str_split(doFoo() ? 'abcdef' : 'ghijkl', 1); assertType("array{'a', 'b', 'c', 'd', 'e', 'f'}|array{'g', 'h', 'i', 'j', 'k', 'l'}", $strSplitConstantStringWithVariableStringAndConstantSplitLength); $strSplitConstantStringWithVariableStringAndVariableSplitLength = str_split(doFoo() ? 'abcdef' : 'ghijkl', doFoo() ? 1 : 2); - assertType('list', $strSplitConstantStringWithVariableStringAndVariableSplitLength); + assertType('non-empty-list', $strSplitConstantStringWithVariableStringAndVariableSplitLength); } + + /** + * @param non-empty-string $nonEmptyString + * @param non-falsy-string $nonFalsyString + */ + function doFoo( + string $string, + string $nonEmptyString, + string $nonFalsyString, + int $integer, + ):void { + assertType('list', str_split($string)); + assertType('non-empty-list', str_split($nonEmptyString)); + assertType('non-empty-list', str_split($nonFalsyString)); + + assertType('list', str_split($string, $integer)); + assertType('non-empty-list', str_split($nonEmptyString, $integer)); + assertType('non-empty-list', str_split($nonFalsyString, $integer)); + } } diff --git a/tests/PHPStan/Analyser/nsrt/bug-7580-php82.php b/tests/PHPStan/Analyser/nsrt/bug-7580-php82.php new file mode 100644 index 0000000000..40031601b4 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-7580-php82.php @@ -0,0 +1,20 @@ += 8.2 + +declare(strict_types = 1); + +namespace Bug7580TypesPHP82; + +use function PHPStan\Testing\assertType; + +assertType('array{}', mb_str_split('', 1)); + +assertType('array{\'x\'}', mb_str_split('x', 1)); + +$v = (string) (mt_rand() === 0 ? '' : 'x'); +assertType('\'\'|\'x\'', $v); +assertType('array{}|array{\'x\'}', mb_str_split($v, 1)); + +function x(): string { throw new \Exception(); }; +$v = x(); +assertType('string', $v); +assertType('list', mb_str_split($v, 1)); diff --git a/tests/PHPStan/Analyser/nsrt/bug-7580.php b/tests/PHPStan/Analyser/nsrt/bug-7580.php index 26fdbc18cb..1bfc0b7544 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-7580.php +++ b/tests/PHPStan/Analyser/nsrt/bug-7580.php @@ -1,4 +1,6 @@ -