Skip to content

Ignore FILTER_THROW_ON_FAILURE bit when the PHP version range only maybe includes PHP 8.5 and the runtime FILTER_FLAG_GLOBAL_RANGE shares it - #6779

Merged
staabm merged 6 commits into
phpstan:2.3.xfrom
phpstan-bot:create-pull-request/patch-tt8irro
Oct 24, 2026
Merged

staabm merged 6 commits into
phpstan:2.3.xfrom
phpstan-bot:create-pull-request/patch-tt8irro

Conversation

@phpstan-bot

Copy link
Copy Markdown
Collaborator

Summary

When PHPStan runs on PHP < 8.5 and the analysed PHP version range only maybe reaches 8.5 (e.g. "php": "^8.4" in composer.json → int<80400, 80699>), FILTER_FLAG_GLOBAL_RANGE was read as FILTER_THROW_ON_FAILURE. filter_var() lost false from its return type and comparisons against false were reported as always true. This was a regression from #6551, which moved the version check to Scope::getPhpVersion(): for a range, the check became maybe, so hasFlag() ran.

Changes

  • src/Type/Php/FilterFunctionReturnTypeHelper.php: hasThrowOnFailureFlag() is now public. It returns no when the scope's PHP versions only maybe have FILTER_THROW_ON_FAILURE and the bit is ambiguous, which means the stubbed FILTER_THROW_ON_FAILURE value equals the runtime FILTER_FLAG_GLOBAL_RANGE value. If the scope is definitely PHP 8.5+ (e.g. inside if (PHP_VERSION_ID >= 80500)), the flag is still detected.
  • src/Type/Php/FilterFunctionsThrowTypeExtension.php: uses the same helper instead of a bare hasFlag('FILTER_THROW_ON_FAILURE'). This fixes the sibling bug where FILTER_FLAG_GLOBAL_RANGE was treated as throwing Filter\FilterFailedException, so a catch for it was not reported as dead.
  • Probed FilterVarRule ("Cannot use both FILTER_NULL_ON_FAILURE and FILTER_THROW_ON_FAILURE"). It is gated on the single configured PhpVersion, which stays below 8.5 for such ranges, so it was not affected in the reported scenario.

Root cause

PHPStan resolves internal constant values from the runtime. On PHP < 8.5, FILTER_FLAG_GLOBAL_RANGE is 0x10000000. PHP 8.5 moved it to 0x20000000 and gave 0x10000000 to FILTER_THROW_ON_FAILURE, which comes from stubs on older runtimes. A bitmask test therefore cannot tell the two flags apart on such a runtime. Before #6551, the version check for a range said "no". Afterwards it said "maybe", and the bitmask test ran anyway. Both consumers of the throw-on-failure check (the return type helper and the throw type extension) now go through one guarded method.

Test

  • tests/PHPStan/Analyser/data/scope-php-version-range-return-type-extensions.php (range 7.4–8.5): filter_var / filter_input_array calls with FILTER_FLAG_GLOBAL_RANGE, including the issue's combined flags, are expected to keep false (or null with FILTER_NULL_ON_FAILURE).
  • tests/PHPStan/Rules/Exceptions/data/throw-type-php-versions.php (range 8.0–8.5): catching Filter\FilterFailedException around filter_var(..., FILTER_FLAG_GLOBAL_RANGE) is reported as a dead catch.
  • Both fail without the fix on a PHP 8.4 runtime and pass with it. They also pass on PHP 8.5, where the constants don't collide. The existing nsrt assertion (int for FILTER_THROW_ON_FAILURE inside PHP_VERSION_ID >= 80500) still passes.

Fixes phpstan/phpstan#15481

Comment thread src/Type/Php/FilterFunctionReturnTypeHelper.php Outdated
Comment thread src/Type/Php/FilterFunctionReturnTypeHelper.php Outdated
Comment thread src/Type/Php/FilterFunctionReturnTypeHelper.php Outdated
@VincentLanglet
VincentLanglet force-pushed the create-pull-request/patch-tt8irro branch 3 times, most recently from ea26c30 to b30c6e1 Compare October 23, 2026 10:55
Comment thread src/Type/Php/FilterFunctionsThrowTypeExtension.php Outdated
@staabm
staabm force-pushed the create-pull-request/patch-tt8irro branch from e6f5722 to 1239402 Compare October 24, 2026 07:28
phpstan-bot and others added 6 commits October 24, 2026 09:49
…maybe includes PHP 8.5 and the runtime `FILTER_FLAG_GLOBAL_RANGE` shares it

- FilterFunctionReturnTypeHelper::hasThrowOnFailureFlag() returns no when
  PhpVersions::hasFilterThrowOnFailureConstant() is maybe and the stubbed
  FILTER_THROW_ON_FAILURE value equals the runtime FILTER_FLAG_GLOBAL_RANGE
  value (PHP < 8.5 runtime, 0x10000000). When the scope is definitely PHP 8.5+,
  the flag is still detected as before.
- FilterFunctionsThrowTypeExtension now uses the same hasThrowOnFailureFlag()
  helper, so FILTER_FLAG_GLOBAL_RANGE is no longer reported as throwing
  Filter\FilterFailedException on such ranges.
- Probed FilterVarRule ("Cannot use both FILTER_NULL_ON_FAILURE and
  FILTER_THROW_ON_FAILURE"): it uses the single configured PhpVersion, which is
  below 8.5 for a range like 8.4-8.5, so it was not affected.
When the PHP version range only maybe includes PHP 8.5 and the runtime
FILTER_FLAG_GLOBAL_RANGE shares its bit, the set bit may mean either flag.
Returning no made a catch of Filter\FilterFailedException around a real
FILTER_THROW_ON_FAILURE call reported as dead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…re a value

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…heck

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@staabm
staabm force-pushed the create-pull-request/patch-tt8irro branch from 421ac34 to 5349772 Compare October 24, 2026 07:49
@staabm
staabm requested a review from VincentLanglet October 24, 2026 07:57
@VincentLanglet
VincentLanglet requested a review from staabm October 24, 2026 12:49
@staabm
staabm merged commit acd0511 into phpstan:2.3.x Oct 24, 2026
991 of 993 checks passed
@staabm
staabm deleted the create-pull-request/patch-tt8irro branch October 24, 2026 12:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Regression of #14397 in 2.3.0: FILTER_FLAG_GLOBAL_RANGE read as FILTER_THROW_ON_FAILURE on PHP 8.4

3 participants