Skip to content

Narrow filter_var first argument type via FunctionTypeSpecifyingExtension when validation filter passes#5512

Closed
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-fl9rdei
Closed

Narrow filter_var first argument type via FunctionTypeSpecifyingExtension when validation filter passes#5512
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-fl9rdei

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

Summary

When filter_var($email, FILTER_VALIDATE_EMAIL) is used in a condition without assignment (e.g., if (false === filter_var($email, FILTER_VALIDATE_EMAIL))), the original $email variable was not narrowed. This PR adds type narrowing for the first argument of filter_var() when a validation filter passes, covering all common comparison patterns.

Changes

  • New FilterVarTypeSpecifyingExtension (src/Type/Php/FilterVarTypeSpecifyingExtension.php): Implements FunctionTypeSpecifyingExtension to narrow the first argument when filter_var() is used in a truthy context. Uses the existing FilterFunctionReturnTypeHelper to determine which filters guarantee non-empty/non-falsy string input.

  • New getInputNarrowingType() method on FilterFunctionReturnTypeHelper (src/Type/Php/FilterFunctionReturnTypeHelper.php): Computes the return type for a generic string input via the existing getType() method, strips failure types (false/null), and returns the appropriate accessory type (non-empty-string or non-falsy-string) if the successful return type is a refined string.

  • TypeSpecifier delegation (src/Analyser/TypeSpecifier.php): Added a case in resolveNormalizedIdentical for filter_var(...) === false that delegates to the function type specifying extension with the appropriate context. This makes patterns like false === filter_var(...) and filter_var(...) !== false trigger the narrowing.

Filters that narrow the input

  • FILTER_VALIDATE_EMAIL → narrows to non-falsy-string
  • FILTER_VALIDATE_IP → narrows to non-falsy-string
  • FILTER_VALIDATE_URL → narrows to non-falsy-string
  • FILTER_VALIDATE_MAC → narrows to non-falsy-string

Filters that correctly do NOT narrow

  • FILTER_VALIDATE_DOMAIN — return type in filter map is string (not non-empty)
  • FILTER_VALIDATE_REGEXP — could match empty string
  • FILTER_VALIDATE_INT, FILTER_VALIDATE_FLOAT, FILTER_VALIDATE_BOOLEAN — return non-string types
  • FILTER_DEFAULT and sanitize filters — not validation filters

Root cause

There was a DynamicFunctionReturnTypeExtension for filter_var that correctly typed the return value (e.g., non-falsy-string|false), but no FunctionTypeSpecifyingExtension to propagate type narrowing back to the function's arguments when used in a condition. Additionally, the TypeSpecifier's resolveNormalizedIdentical had no case for filter_var(...) === false, so even with the extension, the === false comparison pattern would not trigger it.

Test

Comprehensive NSRT test at tests/PHPStan/Analyser/nsrt/bug-14486.php covering:

  • false === filter_var($email, FILTER_VALIDATE_EMAIL) + throw (the exact issue pattern)
  • filter_var($email, ...) !== false comparison
  • if (filter_var($email, ...)) direct truthy check
  • if (!filter_var($email, ...)) negated check
  • Analogous filters: FILTER_VALIDATE_IP, FILTER_VALIDATE_URL, FILTER_VALIDATE_MAC
  • Non-narrowing cases: FILTER_VALIDATE_DOMAIN, FILTER_VALIDATE_REGEXP, FILTER_DEFAULT, FILTER_VALIDATE_INT, FILTER_SANITIZE_EMAIL
  • Falsy branch (filter failed) correctly does not narrow

Fixes phpstan/phpstan#14486

…tension` when validation filter passes

- Add `FilterVarTypeSpecifyingExtension` implementing `FunctionTypeSpecifyingExtension` to narrow the first argument of `filter_var()` when the function result is truthy (validation passed)
- Add `getInputNarrowingType()` to `FilterFunctionReturnTypeHelper` to determine the appropriate narrowing type based on the filter constant and flags
- Add delegation in `TypeSpecifier::resolveNormalizedIdentical` for `filter_var(...) === false` pattern to propagate narrowing from the comparison to the function argument
- Covers FILTER_VALIDATE_EMAIL, FILTER_VALIDATE_IP, FILTER_VALIDATE_URL, FILTER_VALIDATE_MAC (all narrow to non-falsy-string)
- Correctly does NOT narrow for FILTER_VALIDATE_DOMAIN, FILTER_VALIDATE_REGEXP (return type is just string), FILTER_VALIDATE_INT/FLOAT/BOOLEAN (return type is not string), FILTER_DEFAULT, and sanitize filters
@staabm staabm deleted the create-pull-request/patch-fl9rdei branch April 22, 2026 17:44
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.

2 participants