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 00bfb3f33b..002340465b 100644 --- a/src/Type/Php/SubstrDynamicReturnTypeExtension.php +++ b/src/Type/Php/SubstrDynamicReturnTypeExtension.php @@ -24,6 +24,7 @@ use function in_array; use function is_bool; use function mb_substr; +use function strlen; use function substr; #[AutowiredService] @@ -76,19 +77,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); } @@ -129,4 +140,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/LooseConstComparisonPhp7Test.php index 582997ffa3..3b4f48d07a 100644 --- a/tests/PHPStan/Analyser/LooseConstComparisonPhp7Test.php +++ b/tests/PHPStan/Analyser/LooseConstComparisonPhp7Test.php @@ -34,7 +34,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/LooseConstComparisonPhp8Test.php index 549a1d2de4..fae702cec2 100644 --- a/tests/PHPStan/Analyser/LooseConstComparisonPhp8Test.php +++ b/tests/PHPStan/Analyser/LooseConstComparisonPhp8Test.php @@ -34,7 +34,7 @@ public function testFileAsserts( public static function getAdditionalConfigFiles(): array { return [ - __DIR__ . '/looseConstComparisonPhp8.neon', + __DIR__ . '/nodeScopeResolverPhp8.neon', ]; } diff --git a/tests/PHPStan/Analyser/SubstrPhp7Test.php b/tests/PHPStan/Analyser/SubstrPhp7Test.php new file mode 100644 index 0000000000..561b33500c --- /dev/null +++ b/tests/PHPStan/Analyser/SubstrPhp7Test.php @@ -0,0 +1,39 @@ +> + */ + public static function dataFileAsserts(): iterable + { + yield from self::gatherAssertTypes(__DIR__ . '/data/bug-13129-php7.php'); + } + + /** + * @param mixed ...$args + */ + #[DataProvider('dataFileAsserts')] + public function testFileAsserts( + string $assertType, + string $file, + ...$args, + ): void + { + $this->assertFileAsserts($assertType, $file, ...$args); + } + + public static function getAdditionalConfigFiles(): array + { + return [ + __DIR__ . '/nodeScopeResolverPhp7.neon', + ]; + } + +} diff --git a/tests/PHPStan/Analyser/SubstrPhp8Test.php b/tests/PHPStan/Analyser/SubstrPhp8Test.php new file mode 100644 index 0000000000..934a7ac78c --- /dev/null +++ b/tests/PHPStan/Analyser/SubstrPhp8Test.php @@ -0,0 +1,39 @@ +> + */ + public static function dataFileAsserts(): iterable + { + yield from self::gatherAssertTypes(__DIR__ . '/data/bug-13129-php8.php'); + } + + /** + * @param mixed ...$args + */ + #[DataProvider('dataFileAsserts')] + public function testFileAsserts( + string $assertType, + string $file, + ...$args, + ): void + { + $this->assertFileAsserts($assertType, $file, ...$args); + } + + public static function getAdditionalConfigFiles(): array + { + return [ + __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 @@ +