From b650945361334f3d42cb88ded847cd1e346bf127 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 28 Feb 2026 13:57:14 +0000 Subject: [PATCH] Fix array_filter with assert-if-true callable marking all keys optional - When array_filter callback has @phpstan-assert-if-true, check the falsey branch to determine if the callback can actually return false for the given input type - If filtering by falsey value makes the item/key type NeverType, the false branch is impossible, so the key should not be marked optional - New regression test in tests/PHPStan/Analyser/nsrt/bug-11730.php Closes https://github.com/phpstan/phpstan/issues/11730 --- .../ArrayFilterFunctionReturnTypeHelper.php | 18 ++++++++++---- tests/PHPStan/Analyser/nsrt/bug-11730.php | 24 +++++++++++++++++++ 2 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-11730.php diff --git a/src/Type/Php/ArrayFilterFunctionReturnTypeHelper.php b/src/Type/Php/ArrayFilterFunctionReturnTypeHelper.php index 04ef90fef5..6c71da8a1c 100644 --- a/src/Type/Php/ArrayFilterFunctionReturnTypeHelper.php +++ b/src/Type/Php/ArrayFilterFunctionReturnTypeHelper.php @@ -260,12 +260,22 @@ private function processKeyAndItemType(MutatingScope $scope, Type $keyType, Type return [new NeverType(), new NeverType(), false]; } - $scope = $scope->filterByTruthyValue($expr); + $truthyScope = $scope->filterByTruthyValue($expr); + + $optional = !$booleanResult->isTrue()->yes(); + if ($optional) { + $falseyScope = $scope->filterByFalseyValue($expr); + $falseyItemType = $itemVarName !== null ? $falseyScope->getVariableType($itemVarName) : $itemType; + $falseyKeyType = $keyVarName !== null ? $falseyScope->getVariableType($keyVarName) : $keyType; + if ($falseyItemType instanceof NeverType || $falseyKeyType instanceof NeverType) { + $optional = false; + } + } return [ - $keyVarName !== null ? $scope->getVariableType($keyVarName) : $keyType, - $itemVarName !== null ? $scope->getVariableType($itemVarName) : $itemType, - !$booleanResult->isTrue()->yes(), + $keyVarName !== null ? $truthyScope->getVariableType($keyVarName) : $keyType, + $itemVarName !== null ? $truthyScope->getVariableType($itemVarName) : $itemType, + $optional, ]; } diff --git a/tests/PHPStan/Analyser/nsrt/bug-11730.php b/tests/PHPStan/Analyser/nsrt/bug-11730.php new file mode 100644 index 0000000000..42299b511e --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-11730.php @@ -0,0 +1,24 @@ += 8.1 + +declare(strict_types = 1); + +namespace Bug11730; + +use function PHPStan\Testing\assertType; + +class Foo {} + +/** @return ($value is Foo ? true : false) */ +function isFoo(mixed $value): bool { + return $value instanceof Foo; +} + +/** @phpstan-assert-if-true Foo $value */ +function checkFoo(mixed $value): bool { + return $value instanceof Foo; +} + +$data = [new Foo, new Foo]; + +assertType('array{Bug11730\Foo, Bug11730\Foo}', array_filter($data, isFoo(...))); +assertType('array{Bug11730\Foo, Bug11730\Foo}', array_filter($data, checkFoo(...)));