diff --git a/build/baseline-8.0.neon b/build/baseline-8.0.neon index 33bae32309..79d183a1e4 100644 --- a/build/baseline-8.0.neon +++ b/build/baseline-8.0.neon @@ -16,7 +16,7 @@ parameters: path: ../src/Type/Php/MbStrlenFunctionReturnTypeExtension.php - - message: "#^Strict comparison using \\=\\=\\= between non-empty-array and false will always evaluate to false\\.$#" + message: "#^Strict comparison using \\=\\=\\= between array and false will always evaluate to false\\.$#" count: 1 path: ../src/Type/Php/StrSplitFunctionReturnTypeExtension.php diff --git a/resources/functionMap_php74delta.php b/resources/functionMap_php74delta.php index ff3f99ba0d..f86a9c1db2 100644 --- a/resources/functionMap_php74delta.php +++ b/resources/functionMap_php74delta.php @@ -39,7 +39,7 @@ 'FFI::typeof' => ['FFI\CType', '&ptr'=>'FFI\CData'], 'FFI::type' => ['FFI\CType', 'type'=>'string'], 'get_mangled_object_vars' => ['array', 'obj'=>'object'], - 'mb_str_split' => ['non-empty-array|false', 'str'=>'string', 'split_length='=>'int', 'encoding='=>'string'], + 'mb_str_split' => ['array|false', 'str'=>'string', 'split_length='=>'int', 'encoding='=>'string'], 'password_algos' => ['array'], 'password_hash' => ['string|false', 'password'=>'string', 'algo'=>'string|null', 'options='=>'array'], 'password_needs_rehash' => ['bool', 'hash'=>'string', 'algo'=>'string|null', 'options='=>'array'], diff --git a/resources/functionMap_php80delta.php b/resources/functionMap_php80delta.php index fb557075d8..9a7059ec6f 100644 --- a/resources/functionMap_php80delta.php +++ b/resources/functionMap_php80delta.php @@ -74,7 +74,7 @@ 'imagescale' => ['false|object', 'im'=>'resource', 'new_width'=>'int', 'new_height='=>'int', 'method='=>'int'], 'ldap_set_rebind_proc' => ['bool', 'ldap'=>'resource', 'callback'=>'?callable'], 'mb_decode_numericentity' => ['string|false', 'string'=>'string', 'convmap'=>'array', 'encoding='=>'string'], - 'mb_str_split' => ['non-empty-array', 'str'=>'string', 'split_length='=>'positive-int', 'encoding='=>'string'], + 'mb_str_split' => ['array', 'str'=>'string', 'split_length='=>'positive-int', 'encoding='=>'string'], 'mb_strlen' => ['0|positive-int', 'str'=>'string', 'encoding='=>'string'], 'mktime' => ['int|false', 'hour'=>'int', 'minute='=>'int', 'second='=>'int', 'month='=>'int', 'day='=>'int', 'year='=>'int'], 'odbc_exec' => ['resource|false', 'connection_id'=>'resource', 'query'=>'string'], diff --git a/src/Type/Php/StrSplitFunctionReturnTypeExtension.php b/src/Type/Php/StrSplitFunctionReturnTypeExtension.php index 6f0cc221d5..b756c22a4b 100644 --- a/src/Type/Php/StrSplitFunctionReturnTypeExtension.php +++ b/src/Type/Php/StrSplitFunctionReturnTypeExtension.php @@ -16,10 +16,13 @@ use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\IntegerType; +use PHPStan\Type\IntersectionType; use PHPStan\Type\StringType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; +use PHPStan\Type\TypeTraverser; use PHPStan\Type\TypeUtils; +use PHPStan\Type\UnionType; use function array_map; use function array_unique; use function count; @@ -62,6 +65,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, $splitLength = 1; } + $encoding = null; if ($functionReflection->getName() === 'mb_str_split') { if (count($functionCall->getArgs()) >= 3) { $strings = TypeUtils::getConstantStrings($scope->getType($functionCall->getArgs()[2]->value)); @@ -85,22 +89,31 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, } $stringType = $scope->getType($functionCall->getArgs()[0]->value); - if (!$stringType instanceof ConstantStringType) { - return TypeCombinator::intersect( - new ArrayType(new IntegerType(), new StringType()), - new NonEmptyArrayType(), - ); - } - $stringValue = $stringType->getValue(); - $items = isset($encoding) - ? mb_str_split($stringValue, $splitLength, $encoding) - : str_split($stringValue, $splitLength); - if ($items === false) { - throw new ShouldNotHappenException(); - } + return TypeTraverser::map($stringType, static function (Type $type, callable $traverse) use ($encoding, $splitLength, $scope): Type { + if ($type instanceof UnionType || $type instanceof IntersectionType) { + return $traverse($type); + } + + if (!$type instanceof ConstantStringType) { + $returnType = new ArrayType(new IntegerType(), new StringType()); + + return $encoding === null + ? TypeCombinator::intersect($returnType, new NonEmptyArrayType()) + : $returnType; + } + + $stringValue = $type->getValue(); + + $items = $encoding === null + ? str_split($stringValue, $splitLength) + : mb_str_split($stringValue, $splitLength, $encoding); + if ($items === false) { + throw new ShouldNotHappenException(); + } - return self::createConstantArrayFrom($items, $scope); + return self::createConstantArrayFrom($items, $scope); + }); } /** diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index 328d46c9fc..8ce2b2fa38 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -5356,7 +5356,7 @@ public function dataFunctions(): array '$strSplitConstantStringWithInvalidSplitLengthType', ], [ - 'non-empty-array', + 'array{\'a\'|\'g\', \'b\'|\'h\', \'c\'|\'i\', \'d\'|\'j\', \'e\'|\'k\', \'f\'|\'l\'}', '$strSplitConstantStringWithVariableStringAndConstantSplitLength', ], [ @@ -8716,7 +8716,7 @@ public function dataPhp74Functions(): array { return [ [ - PHP_VERSION_ID < 80000 ? 'non-empty-array|false' : 'non-empty-array', + PHP_VERSION_ID < 80000 ? 'array|false' : 'array', '$mbStrSplitConstantStringWithoutDefinedParameters', ], [ @@ -8724,7 +8724,7 @@ public function dataPhp74Functions(): array '$mbStrSplitConstantStringWithoutDefinedSplitLength', ], [ - 'non-empty-array', + 'array', '$mbStrSplitStringWithoutDefinedSplitLength', ], [ @@ -8740,15 +8740,15 @@ public function dataPhp74Functions(): array '$mbStrSplitConstantStringWithFailureSplitLength', ], [ - PHP_VERSION_ID < 80000 ? 'non-empty-array|false' : 'non-empty-array', + PHP_VERSION_ID < 80000 ? 'array|false' : 'array', '$mbStrSplitConstantStringWithInvalidSplitLengthType', ], [ - 'non-empty-array', + 'array{\'a\'|\'g\', \'b\'|\'h\', \'c\'|\'i\', \'d\'|\'j\', \'e\'|\'k\', \'f\'|\'l\'}', '$mbStrSplitConstantStringWithVariableStringAndConstantSplitLength', ], [ - PHP_VERSION_ID < 80000 ? 'non-empty-array|false' : 'non-empty-array', + PHP_VERSION_ID < 80000 ? 'array|false' : 'array', '$mbStrSplitConstantStringWithVariableStringAndVariableSplitLength', ], [ @@ -8760,7 +8760,7 @@ public function dataPhp74Functions(): array '$mbStrSplitConstantStringWithOneSplitLengthAndInvalidEncoding', ], [ - PHP_VERSION_ID < 80000 ? 'non-empty-array|false' : 'non-empty-array', + PHP_VERSION_ID < 80000 ? 'array|false' : 'array', '$mbStrSplitConstantStringWithOneSplitLengthAndVariableEncoding', ], [ @@ -8772,7 +8772,7 @@ public function dataPhp74Functions(): array '$mbStrSplitConstantStringWithGreaterSplitLengthThanStringLengthAndInvalidEncoding', ], [ - PHP_VERSION_ID < 80000 ? 'non-empty-array|false' : 'non-empty-array', + PHP_VERSION_ID < 80000 ? 'array|false' : 'array', '$mbStrSplitConstantStringWithGreaterSplitLengthThanStringLengthAndVariableEncoding', ], [ @@ -8788,7 +8788,7 @@ public function dataPhp74Functions(): array '$mbStrSplitConstantStringWithFailureSplitLengthAndVariableEncoding', ], [ - PHP_VERSION_ID < 80000 ? 'non-empty-array|false' : 'non-empty-array', + PHP_VERSION_ID < 80000 ? 'array|false' : 'array', '$mbStrSplitConstantStringWithInvalidSplitLengthTypeAndValidEncoding', ], [ @@ -8796,11 +8796,11 @@ public function dataPhp74Functions(): array '$mbStrSplitConstantStringWithInvalidSplitLengthTypeAndInvalidEncoding', ], [ - PHP_VERSION_ID < 80000 ? 'non-empty-array|false' : 'non-empty-array', + PHP_VERSION_ID < 80000 ? 'array|false' : 'array', '$mbStrSplitConstantStringWithInvalidSplitLengthTypeAndVariableEncoding', ], [ - 'non-empty-array', + 'array{\'a\'|\'g\', \'b\'|\'h\', \'c\'|\'i\', \'d\'|\'j\', \'e\'|\'k\', \'f\'|\'l\'}', '$mbStrSplitConstantStringWithVariableStringAndConstantSplitLengthAndValidEncoding', ], [ @@ -8808,11 +8808,11 @@ public function dataPhp74Functions(): array '$mbStrSplitConstantStringWithVariableStringAndConstantSplitLengthAndInvalidEncoding', ], [ - PHP_VERSION_ID < 80000 ? 'non-empty-array|false' : 'non-empty-array', + PHP_VERSION_ID < 80000 ? 'array|false' : 'array', '$mbStrSplitConstantStringWithVariableStringAndConstantSplitLengthAndVariableEncoding', ], [ - PHP_VERSION_ID < 80000 ? 'non-empty-array|false' : 'non-empty-array', + PHP_VERSION_ID < 80000 ? 'array|false' : 'array', '$mbStrSplitConstantStringWithVariableStringAndVariableSplitLengthAndValidEncoding', ], [ @@ -8820,7 +8820,7 @@ public function dataPhp74Functions(): array '$mbStrSplitConstantStringWithVariableStringAndVariableSplitLengthAndInvalidEncoding', ], [ - PHP_VERSION_ID < 80000 ? 'non-empty-array|false' : 'non-empty-array', + PHP_VERSION_ID < 80000 ? 'array|false' : 'array', '$mbStrSplitConstantStringWithVariableStringAndVariableSplitLengthAndVariableEncoding', ], ]; diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index e3a08cf635..9ce49a6429 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -923,6 +923,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/emptyiterator.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/collected-data.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-7550.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-7580.php'); } /** diff --git a/tests/PHPStan/Analyser/data/bug-7580.php b/tests/PHPStan/Analyser/data/bug-7580.php new file mode 100644 index 0000000000..e516bffe7d --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-7580.php @@ -0,0 +1,18 @@ +', mb_str_split($v, 1)); diff --git a/tests/PHPStan/Rules/Comparison/TernaryOperatorConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/TernaryOperatorConstantConditionRuleTest.php index 1403768f98..d54153136c 100644 --- a/tests/PHPStan/Rules/Comparison/TernaryOperatorConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/TernaryOperatorConstantConditionRuleTest.php @@ -92,4 +92,19 @@ public function testReportPhpDoc(): void ]); } + public function testBug7580(): void + { + $this->treatPhpDocTypesAsCertain = false; + $this->analyse([__DIR__ . '/data/bug-7580.php'], [ + [ + 'Ternary operator condition is always false.', + 6, + ], + [ + 'Ternary operator condition is always true.', + 9, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-7580.php b/tests/PHPStan/Rules/Comparison/data/bug-7580.php new file mode 100644 index 0000000000..294ed21160 --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-7580.php @@ -0,0 +1,20 @@ +