diff --git a/build/baseline-8.0.neon b/build/baseline-8.0.neon index 95dfa6cf8a..1618897e6e 100644 --- a/build/baseline-8.0.neon +++ b/build/baseline-8.0.neon @@ -29,8 +29,3 @@ parameters: 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 - path: ../src/Type/Php/SubstrDynamicReturnTypeExtension.php diff --git a/src/Type/Php/SubstrDynamicReturnTypeExtension.php b/src/Type/Php/SubstrDynamicReturnTypeExtension.php index df1d0cf060..806070c037 100644 --- a/src/Type/Php/SubstrDynamicReturnTypeExtension.php +++ b/src/Type/Php/SubstrDynamicReturnTypeExtension.php @@ -23,6 +23,7 @@ use function in_array; use function is_bool; use function mb_substr; +use function strlen; use function substr; final class SubstrDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension @@ -74,19 +75,29 @@ public function getTypeFromFunctionCall( if ($length !== null) { if ($functionReflection->getName() === 'mb_substr') { $substr = mb_substr($constantString->getValue(), $offset->getValue(), $length->getValue()); + } elseif ($this->phpVersion->substrReturnFalseInsteadOfEmptyString()) { + $substr = $this->substrOrFalse($constantString->getValue(), $offset->getValue(), $length->getValue()); } else { $substr = substr($constantString->getValue(), $offset->getValue(), $length->getValue()); } } else { if ($functionReflection->getName() === 'mb_substr') { $substr = mb_substr($constantString->getValue(), $offset->getValue()); + } elseif ($this->phpVersion->substrReturnFalseInsteadOfEmptyString()) { + // Simulate substr call on an older PHP version if the runtime one is too new. + $substr = $this->substrOrFalse($constantString->getValue(), $offset->getValue()); } else { $substr = substr($constantString->getValue(), $offset->getValue()); } } if (is_bool($substr)) { - $results[] = new ConstantBooleanType($substr); + if ($this->phpVersion->substrReturnFalseInsteadOfEmptyString()) { + $results[] = new ConstantBooleanType($substr); + } else { + // Simulate substr call on a recent PHP version if the runtime one is too old. + $results[] = new ConstantStringType(''); + } } else { $results[] = new ConstantStringType($substr); } @@ -127,4 +138,28 @@ public function getTypeFromFunctionCall( return null; } + private function substrOrFalse(string $string, int $offset, ?int $length = null): false|string + { + $strlen = strlen($string); + + if ($offset > $strlen) { + return false; + } + + if ($length !== null && $length < 0) { + if ($offset < 0 && -$length > $strlen) { + return false; + } + if ($offset >= 0 && -$length > $strlen - $offset) { + return false; + } + } + + if ($length === null) { + return substr($string, $offset); + } + + return substr($string, $offset, $length); + } + } diff --git a/tests/PHPStan/Analyser/LooseConstComparisonPhp7Test.php b/tests/PHPStan/Analyser/NodeScopeResolverPhp7Test.php similarity index 80% rename from tests/PHPStan/Analyser/LooseConstComparisonPhp7Test.php rename to tests/PHPStan/Analyser/NodeScopeResolverPhp7Test.php index 83bb27b70b..c53630b1e1 100644 --- a/tests/PHPStan/Analyser/LooseConstComparisonPhp7Test.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverPhp7Test.php @@ -4,7 +4,7 @@ use PHPStan\Testing\TypeInferenceTestCase; -class LooseConstComparisonPhp7Test extends TypeInferenceTestCase +class NodeScopeResolverPhp7Test extends TypeInferenceTestCase { /** @@ -15,6 +15,8 @@ public function dataFileAsserts(): iterable // compares constants according to the php-version phpstan configuration, // _NOT_ the current php runtime version yield from $this->gatherAssertTypes(__DIR__ . '/data/loose-const-comparison-php7.php'); + + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-13129-php7.php'); } /** @@ -33,7 +35,7 @@ public function testFileAsserts( public static function getAdditionalConfigFiles(): array { return [ - __DIR__ . '/looseConstComparisonPhp7.neon', + __DIR__ . '/nodeScopeResolverPhp7.neon', ]; } diff --git a/tests/PHPStan/Analyser/LooseConstComparisonPhp8Test.php b/tests/PHPStan/Analyser/NodeScopeResolverPhp8Test.php similarity index 80% rename from tests/PHPStan/Analyser/LooseConstComparisonPhp8Test.php rename to tests/PHPStan/Analyser/NodeScopeResolverPhp8Test.php index e765ca01d5..851c6b7204 100644 --- a/tests/PHPStan/Analyser/LooseConstComparisonPhp8Test.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverPhp8Test.php @@ -4,7 +4,7 @@ use PHPStan\Testing\TypeInferenceTestCase; -class LooseConstComparisonPhp8Test extends TypeInferenceTestCase +class NodeScopeResolverPhp8Test extends TypeInferenceTestCase { /** @@ -15,6 +15,8 @@ public function dataFileAsserts(): iterable // compares constants according to the php-version phpstan configuration, // _NOT_ the current php runtime version yield from $this->gatherAssertTypes(__DIR__ . '/data/loose-const-comparison-php8.php'); + + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-13129-php8.php'); } /** @@ -33,7 +35,7 @@ public function testFileAsserts( public static function getAdditionalConfigFiles(): array { return [ - __DIR__ . '/looseConstComparisonPhp8.neon', + __DIR__ . '/nodeScopeResolverPhp8.neon', ]; } diff --git a/tests/PHPStan/Analyser/data/bug-13129-php7.php b/tests/PHPStan/Analyser/data/bug-13129-php7.php new file mode 100644 index 0000000000..c12e1db1a6 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-13129-php7.php @@ -0,0 +1,9 @@ +