From fe77aec32180c8fb353dfafb8b6d43e886c5d823 Mon Sep 17 00:00:00 2001 From: HawkForge <75696486+HawkForge@users.noreply.github.com> Date: Thu, 15 Oct 2026 17:30:55 +0600 Subject: [PATCH] Keep the failure value of filter_var() unless every PHP version has FILTER_THROW_ON_FAILURE hasThrowOnFailureFlag() only returned no when none of the analysed PHP versions has FILTER_THROW_ON_FAILURE. For a range like 8.4 - 8.5 it checked the flag bit as if every version had it. Before PHP 8.5 that bit (0x10000000) is FILTER_FLAG_GLOBAL_RANGE, so on PHP 8.4 a call with FILTER_FLAG_GLOBAL_RANGE lost false from its return type and comparing the result with false was reported as always true. The flag result is now combined with the PHP version check, so it is only yes when every analysed version throws instead of returning the failure value. Closes https://github.com/phpstan/phpstan/issues/15481 --- .../Php/FilterFunctionReturnTypeHelper.php | 10 ++++-- tests/PHPStan/Analyser/Bug15481Test.php | 36 +++++++++++++++++++ tests/PHPStan/Analyser/bug-15481.neon | 4 +++ tests/PHPStan/Analyser/data/bug-15481.php | 18 ++++++++++ 4 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 tests/PHPStan/Analyser/Bug15481Test.php create mode 100644 tests/PHPStan/Analyser/bug-15481.neon create mode 100644 tests/PHPStan/Analyser/data/bug-15481.php diff --git a/src/Type/Php/FilterFunctionReturnTypeHelper.php b/src/Type/Php/FilterFunctionReturnTypeHelper.php index af128a42272..a36981b6d20 100644 --- a/src/Type/Php/FilterFunctionReturnTypeHelper.php +++ b/src/Type/Php/FilterFunctionReturnTypeHelper.php @@ -517,9 +517,13 @@ public function hasFlag(string $flagName, ?Type $flagsType): TrinaryLogic private function hasThrowOnFailureFlag(?Type $flagsType, PhpVersions $phpVersions): TrinaryLogic { - return $phpVersions->hasFilterThrowOnFailureConstant()->no() - ? TrinaryLogic::createNo() - : $this->hasFlag('FILTER_THROW_ON_FAILURE', $flagsType); + $hasThrowOnFailureConstant = $phpVersions->hasFilterThrowOnFailureConstant(); + if ($hasThrowOnFailureConstant->no()) { + return TrinaryLogic::createNo(); + } + + // before PHP 8.5, this bit is FILTER_FLAG_GLOBAL_RANGE and the failure value is still returned + return $hasThrowOnFailureConstant->and($this->hasFlag('FILTER_THROW_ON_FAILURE', $flagsType)); } private function getFlagsValue(Type $exprType): Type diff --git a/tests/PHPStan/Analyser/Bug15481Test.php b/tests/PHPStan/Analyser/Bug15481Test.php new file mode 100644 index 00000000000..749636f3e05 --- /dev/null +++ b/tests/PHPStan/Analyser/Bug15481Test.php @@ -0,0 +1,36 @@ +assertFileAsserts($assertType, $file, ...$args); + } + + public static function getAdditionalConfigFiles(): array + { + return [ + __DIR__ . '/bug-15481.neon', + ]; + } + +} diff --git a/tests/PHPStan/Analyser/bug-15481.neon b/tests/PHPStan/Analyser/bug-15481.neon new file mode 100644 index 00000000000..43060cc0b5c --- /dev/null +++ b/tests/PHPStan/Analyser/bug-15481.neon @@ -0,0 +1,4 @@ +parameters: + phpVersion: + min: 80400 + max: 80599 diff --git a/tests/PHPStan/Analyser/data/bug-15481.php b/tests/PHPStan/Analyser/data/bug-15481.php new file mode 100644 index 00000000000..26a6bb9bea7 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-15481.php @@ -0,0 +1,18 @@ +