Skip to content

Narrow array type based on array_all() and array_any() callback type assertions#5624

Open
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-39z9729
Open

Narrow array type based on array_all() and array_any() callback type assertions#5624
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-39z9729

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

Summary

When array_all($array, $callback) returns true, every element in $array satisfies the callback. If the callback narrows types (e.g. is_int(...), a closure with instanceof, or a function with @phpstan-assert-if-true), the array's value and/or key types should be narrowed accordingly. Similarly, when array_any($array, $callback) returns false, no element satisfies the callback, so the falsey branch of the callback applies to all elements.

Changes

  • Added src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php — a new FunctionTypeSpecifyingExtension that handles:
    • array_all() in truthy context: narrows array types based on the callback's truthy branch
    • array_any() in falsey context: narrows array types based on the callback's falsey branch
  • The extension extracts the callback expression from all supported forms:
    • Arrow functions (fn ($v) => is_int($v))
    • Closures (function ($v) { return is_int($v); })
    • First-class callables (is_int(...), isInt(...))
    • String callables ('is_int')
  • It creates a temporary scope with the array's key/value types assigned to variables, then uses filterByTruthyValue/filterByFalseyValue to determine the narrowed types
  • Constant array types preserve their structure and optional key status
  • List and non-empty-array accessory types are preserved through type intersection

Root cause

There was no FunctionTypeSpecifyingExtension registered for array_all() or array_any(). The functions returned bool but PHPStan had no mechanism to narrow the array argument's type based on the callback's type assertions. The fix adds an extension following the same pattern used by other type-specifying extensions (ArraySearchFunctionTypeSpecifyingExtension, InArrayFunctionTypeSpecifyingExtension, etc.) and reuses the scope filtering mechanism already used by ArrayFilterFunctionReturnTypeHelper.

Analogous cases probed

  • array_all falsey context: Correctly does NOT narrow (we only know at least one element doesn't match) — verified with test
  • array_any truthy context: Correctly does NOT narrow (we only know at least one matches) — verified with test
  • Key narrowing: Works for both array_all and array_any when the callback inspects the second parameter — tested
  • Constant arrays: Shape and optional keys are preserved during narrowing — tested
  • assert(array_all(...))): Works via the existing assert() type specifying infrastructure — tested
  • array_find / array_find_key: These return the found element/key, not a boolean — they don't benefit from this type of narrowing

Test

Added tests/PHPStan/Analyser/nsrt/array-all-type-narrowing.php with 20 test functions covering:

  • All callback forms (arrow function, closure, first-class callable, string callable, @phpstan-assert-if-true)
  • Value narrowing (is_int, is_string, instanceof)
  • Key narrowing and combined key+value narrowing
  • array_any falsey narrowing
  • Non-narrowing cases (array_all falsey, array_any truthy)
  • Constant array narrowing with optional keys
  • assert(array_all(...)) usage
  • Preservation of list type and non-empty-array type

Fixes phpstan/phpstan#12939

…ype assertions

- Add ArrayAllFunctionTypeSpecifyingExtension that narrows array key/value
  types when array_all() is used in a truthy context or array_any() in a
  falsey context
- Support all callback forms: closures, arrow functions, first-class
  callables, string callables, and @phpstan-assert-if-true annotations
- Narrow both value types and key types when the callback inspects both
- Preserve constant array structure and optional key status during narrowing
- Preserve list and non-empty-array accessory types through intersection
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