From 1caf455da913bb47377cb17ae31b12f8b2325e4a Mon Sep 17 00:00:00 2001 From: uekann Date: Thu, 5 Mar 2026 15:48:10 +0900 Subject: [PATCH 01/17] Add simple implementation and test cases --- ...rrayAllFunctionTypeSpecifyingExtension.php | 65 +++++++++++++++++++ tests/PHPStan/Analyser/TypeSpecifierTest.php | 41 ++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php diff --git a/src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php b/src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php new file mode 100644 index 00000000000..8afb88c26a7 --- /dev/null +++ b/src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php @@ -0,0 +1,65 @@ +getName()) === 'array_all' + && !$context->null(); + } + + public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes + { + $args = $node->getArgs(); + if (!$context->true() || count($args) < 2) { + return new SpecifiedTypes(); + } + + $array = $args[0]->value; + $callable = $args[1]->value; + if ($callable instanceof Expr\ArrowFunction && $callable->expr instanceof Expr\FuncCall) { + $specifiedTypesInCallable = $this->typeSpecifier->specifyTypesInCondition($scope, $callable->expr, $context)->getSureTypes(); + $callableParm = $callable->params[0]; + if (!$callableParm instanceof Expr\Variable || !key_exists("$" . $callableParm->name, $specifiedTypesInCallable)) { + return new SpecifiedTypes(); + } + $ItemType = $specifiedTypesInCallable["$" . $callableParm->name][1]; + return $this->typeSpecifier->create( + $array, + new ArrayType(new MixedType(), $ItemType), + $context, + $scope + ); + } + + return new SpecifiedTypes(); + } + + public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void + { + $this->typeSpecifier = $typeSpecifier; + } + +} diff --git a/tests/PHPStan/Analyser/TypeSpecifierTest.php b/tests/PHPStan/Analyser/TypeSpecifierTest.php index aa66c940016..945a7ff7b2c 100644 --- a/tests/PHPStan/Analyser/TypeSpecifierTest.php +++ b/tests/PHPStan/Analyser/TypeSpecifierTest.php @@ -26,6 +26,7 @@ use PHPStan\Type\ArrayType; use PHPStan\Type\ClassStringType; use PHPStan\Type\Constant\ConstantBooleanType; +use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\FloatType; use PHPStan\Type\Generic\GenericClassStringType; use PHPStan\Type\IntegerType; @@ -1320,6 +1321,46 @@ public static function dataCondition(): iterable ], [], ], + [ + new FuncCall( + new Name("array_all"), + [ + new Arg(new Variable("array")), + new Arg( + new Expr\ArrowFunction( + [ + 'expr' => new FuncCall(new Name("is_int"), [new Arg(new Variable("value"))]), + 'params' => [new Variable("value")], + ], + [] + ) + ) + ] + ), + [ + '$array' => 'array' + ], + [] + ], + [ + new FuncCall( + new Name("array_all"), + [ + new Arg(new Variable("array")), + new Arg( + new Expr\ArrowFunction( + [ + 'expr' => new FuncCall(new Name("is_int"), [new Arg(new Expr\ConstFetch(new Name("1")))]), + 'params' => [new Variable("value")], + ], + [] + ) + ) + ] + ), + [], + [] + ], ]; } From 5b3ffca34ebf811e21658b08a52084c6a16dce3c Mon Sep 17 00:00:00 2001 From: uekann Date: Thu, 5 Mar 2026 17:01:34 +0900 Subject: [PATCH 02/17] Implement of type specifacation for array's key --- ...rrayAllFunctionTypeSpecifyingExtension.php | 52 +++++++++++++++---- tests/PHPStan/Analyser/TypeSpecifierTest.php | 22 ++++++++ 2 files changed, 63 insertions(+), 11 deletions(-) diff --git a/src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php b/src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php index 8afb88c26a7..68c8feb706f 100644 --- a/src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php @@ -39,19 +39,49 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n $array = $args[0]->value; $callable = $args[1]->value; + + if ($callable instanceof Expr\ArrowFunction && $callable->expr instanceof Expr\FuncCall) { - $specifiedTypesInCallable = $this->typeSpecifier->specifyTypesInCondition($scope, $callable->expr, $context)->getSureTypes(); - $callableParm = $callable->params[0]; - if (!$callableParm instanceof Expr\Variable || !key_exists("$" . $callableParm->name, $specifiedTypesInCallable)) { - return new SpecifiedTypes(); + + $callableParms = $callable->params; + $specifiedTypesInFuncCall = $this->typeSpecifier->specifyTypesInCondition($scope, $callable->expr, $context)->getSureTypes(); + + if(count($callableParms) >= 1 && $callableParms[0] instanceof Expr\Variable) { + + $callableParmValueName = $callableParms[0]->name; + $specifiedTypeOfValue = array_find( + $specifiedTypesInFuncCall, + fn($specifiedType) => $specifiedType[0] instanceof Expr\Variable && $specifiedType[0]->name === $callableParmValueName + ); + + if(isset($specifiedTypeOfValue)) { + $valueType = $specifiedTypeOfValue[1]; + } + + } + + if(count($callableParms) >= 2 && $callableParms[1] instanceof Expr\Variable) { + + $callableParmKeyName = $callableParms[1]->name; + $specifiedTypeOfKey = array_find( + $specifiedTypesInFuncCall, + fn($specifiedType) => $specifiedType[0] instanceof Expr\Variable && $specifiedType[0]->name === $callableParmKeyName + ); + + if(isset($specifiedTypeOfKey)) { + $keyType = $specifiedTypeOfKey[1]; + } + + } + + if(isset($keyType) || isset($valueType)) { + return $this->typeSpecifier->create( + $array, + new ArrayType($keyType ?? new MixedType(), $valueType ?? new MixedType()), + $context, + $scope + ); } - $ItemType = $specifiedTypesInCallable["$" . $callableParm->name][1]; - return $this->typeSpecifier->create( - $array, - new ArrayType(new MixedType(), $ItemType), - $context, - $scope - ); } return new SpecifiedTypes(); diff --git a/tests/PHPStan/Analyser/TypeSpecifierTest.php b/tests/PHPStan/Analyser/TypeSpecifierTest.php index 945a7ff7b2c..fa48f234d0d 100644 --- a/tests/PHPStan/Analyser/TypeSpecifierTest.php +++ b/tests/PHPStan/Analyser/TypeSpecifierTest.php @@ -17,6 +17,7 @@ use PhpParser\Node\Name; use PhpParser\Node\Scalar\LNumber; use PhpParser\Node\Scalar\String_; +use PhpParser\Node\VariadicPlaceholder; use PhpParser\Node\VarLikeIdentifier; use PhpParser\PrettyPrinter\Standard; use PHPStan\Node\Expr\AlwaysRememberedExpr; @@ -1342,6 +1343,27 @@ public static function dataCondition(): iterable ], [] ], + [ + new FuncCall( + new Name("array_all"), + [ + new Arg(new Variable("array")), + new Arg( + new Expr\ArrowFunction( + [ + 'expr' => new FuncCall(new Name("is_string"), [new Arg(new Variable("key"))]), + 'params' => [new Variable("value"), new Variable("key")], + ], + [] + ) + ) + ] + ), + [ + '$array' => 'array' + ], + [] + ], [ new FuncCall( new Name("array_all"), From 4224d8c65284d10db9f6e267a5360cf2d22b2df3 Mon Sep 17 00:00:00 2001 From: uekann Date: Thu, 5 Mar 2026 18:20:10 +0900 Subject: [PATCH 03/17] Support for arrow functions containing arbitrary expr --- ...rrayAllFunctionTypeSpecifyingExtension.php | 43 ++++++++++- tests/PHPStan/Analyser/TypeSpecifierTest.php | 72 +++++++++++++++++++ 2 files changed, 114 insertions(+), 1 deletion(-) diff --git a/src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php b/src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php index 68c8feb706f..538ab3deb7a 100644 --- a/src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php @@ -4,12 +4,14 @@ use PhpParser\Node\Expr; use PhpParser\Node\Expr\FuncCall; +use PhpParser\Node\Expr\Variable; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierAwareExtension; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; +use PHPStan\Node\Expr\TypeExpr; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\ShouldNotHappenException; @@ -38,12 +40,39 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n } $array = $args[0]->value; + $arrayArgType = $scope->getType($array); + $arrayTypes = $arrayArgType->getArrays(); + + if(count($arrayTypes) === 0) { + return new SpecifiedTypes(); + } + $callable = $args[1]->value; - if ($callable instanceof Expr\ArrowFunction && $callable->expr instanceof Expr\FuncCall) { + // if ($callable instanceof Expr\ArrowFunction && $callable->expr instanceof Expr\FuncCall) { + if ($callable instanceof Expr\ArrowFunction) { $callableParms = $callable->params; + + // foreach($arrayTypes as $arrayType) { + // $keyType = $arrayType->getKeyType(); + // $itemType = $arrayType->getItemType(); + // + // $variables = []; + // + // if (count($callableParms) < 1) { + // continue; + // } + // + // if ($callableParms[0] ?? null instanceof Expr\Variable) { + // $variables[$callableParms[0]->name] = $itemType; + // } + // + // if ($callableParms[1] ?? null instanceof Expr\Variable) { + // $variables[$callableParms[1]->name] = $keyType; + // } + // } $specifiedTypesInFuncCall = $this->typeSpecifier->specifyTypesInCondition($scope, $callable->expr, $context)->getSureTypes(); if(count($callableParms) >= 1 && $callableParms[0] instanceof Expr\Variable) { @@ -87,6 +116,18 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n return new SpecifiedTypes(); } + // /** + // * @param array $paramTypes + // */ + // private function replaceVariable(Expr $expr, array $paramTypes) : Expr { + // if ($expr instanceof Variable) { + // $variableName = $expr->name; + // if (isset($paramTypes[$variableName])) { + // return $paramTypes[$variableName]; + // } + // } elseif ($expr instanceof ) + // } + public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void { $this->typeSpecifier = $typeSpecifier; diff --git a/tests/PHPStan/Analyser/TypeSpecifierTest.php b/tests/PHPStan/Analyser/TypeSpecifierTest.php index fa48f234d0d..228c76aed61 100644 --- a/tests/PHPStan/Analyser/TypeSpecifierTest.php +++ b/tests/PHPStan/Analyser/TypeSpecifierTest.php @@ -1364,6 +1364,78 @@ public static function dataCondition(): iterable ], [] ], + [ + new FuncCall( + new Name("array_all"), + [ + new Arg(new Variable("array")), + new Arg( + new Expr\ArrowFunction( + [ + 'expr' => new Expr\BinaryOp\BooleanAnd( + new FuncCall(new Name("is_int"), [new Arg(new Variable("value"))]), + new FuncCall(new Name("is_string"), [new Arg(new Variable("key"))]), + ), + 'params' => [new Variable("value"), new Variable("key")], + ], + [] + ) + ) + ] + ), + [ + '$array' => 'array' + ], + [] + ], + [ + new FuncCall( + new Name("array_all"), + [ + new Arg(new Variable("array")), + new Arg( + new Expr\ArrowFunction( + [ + 'expr' => new Expr\BinaryOp\BooleanAnd( + new FuncCall(new Name("is_string"), [new Arg(new Variable("value"))]), + new FuncCall(new Name("is_numeric"), [new Arg(new Variable("value"))]), + ), + 'params' => [new Variable("value"), new Variable("key")], + ], + [] + ) + ) + ] + ), + [ + '$array' => 'array' + ], + [] + ], + [ + new FuncCall( + new Name("array_all"), + [ + new Arg(new Variable("array")), + new Arg( + new Expr\ArrowFunction( + [ + 'expr' => new Expr\BinaryOp\BooleanOr( + new FuncCall(new Name("is_float"), [new Arg(new Variable("value"))]), + new FuncCall(new Name("is_bool"), [new Arg(new Variable("value"))]), + ), + 'params' => [new Variable("value"), new Variable("key")], + ], + [] + ) + ) + ] + ), + [ + '$array' => 'array' + ], + [] + ], [ new FuncCall( new Name("array_all"), From baf2d90c8c03d4105086beb13fc81d28602760c9 Mon Sep 17 00:00:00 2001 From: uekann Date: Thu, 5 Mar 2026 18:45:45 +0900 Subject: [PATCH 04/17] Refactor --- ...rrayAllFunctionTypeSpecifyingExtension.php | 89 ++++--------- tests/PHPStan/Analyser/TypeSpecifierTest.php | 126 +++++++++--------- 2 files changed, 88 insertions(+), 127 deletions(-) diff --git a/src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php b/src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php index 538ab3deb7a..eb6fd453374 100644 --- a/src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php @@ -4,20 +4,19 @@ use PhpParser\Node\Expr; use PhpParser\Node\Expr\FuncCall; -use PhpParser\Node\Expr\Variable; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierAwareExtension; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; -use PHPStan\Node\Expr\TypeExpr; use PHPStan\Reflection\FunctionReflection; -use PHPStan\Type\Constant\ConstantBooleanType; -use PHPStan\ShouldNotHappenException; use PHPStan\Type\ArrayType; use PHPStan\Type\FunctionTypeSpecifyingExtension; use PHPStan\Type\MixedType; +use PHPStan\Type\Type; +use function array_find; +use function count; use function strtolower; #[AutowiredService] @@ -43,72 +42,31 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n $arrayArgType = $scope->getType($array); $arrayTypes = $arrayArgType->getArrays(); - if(count($arrayTypes) === 0) { + if (count($arrayTypes) === 0) { return new SpecifiedTypes(); } $callable = $args[1]->value; - - // if ($callable instanceof Expr\ArrowFunction && $callable->expr instanceof Expr\FuncCall) { if ($callable instanceof Expr\ArrowFunction) { $callableParms = $callable->params; - - // foreach($arrayTypes as $arrayType) { - // $keyType = $arrayType->getKeyType(); - // $itemType = $arrayType->getItemType(); - // - // $variables = []; - // - // if (count($callableParms) < 1) { - // continue; - // } - // - // if ($callableParms[0] ?? null instanceof Expr\Variable) { - // $variables[$callableParms[0]->name] = $itemType; - // } - // - // if ($callableParms[1] ?? null instanceof Expr\Variable) { - // $variables[$callableParms[1]->name] = $keyType; - // } - // } $specifiedTypesInFuncCall = $this->typeSpecifier->specifyTypesInCondition($scope, $callable->expr, $context)->getSureTypes(); - if(count($callableParms) >= 1 && $callableParms[0] instanceof Expr\Variable) { - - $callableParmValueName = $callableParms[0]->name; - $specifiedTypeOfValue = array_find( - $specifiedTypesInFuncCall, - fn($specifiedType) => $specifiedType[0] instanceof Expr\Variable && $specifiedType[0]->name === $callableParmValueName - ); - - if(isset($specifiedTypeOfValue)) { - $valueType = $specifiedTypeOfValue[1]; - } - + if (isset($callableParms[0]) && $callableParms[0] instanceof Expr\Variable) { + $valueType = $this->fetchTypeByVariable($specifiedTypesInFuncCall, $callableParms[0]->name); } - if(count($callableParms) >= 2 && $callableParms[1] instanceof Expr\Variable) { - - $callableParmKeyName = $callableParms[1]->name; - $specifiedTypeOfKey = array_find( - $specifiedTypesInFuncCall, - fn($specifiedType) => $specifiedType[0] instanceof Expr\Variable && $specifiedType[0]->name === $callableParmKeyName - ); - - if(isset($specifiedTypeOfKey)) { - $keyType = $specifiedTypeOfKey[1]; - } - + if (isset($callableParms[1]) && $callableParms[1] instanceof Expr\Variable) { + $keyType = $this->fetchTypeByVariable($specifiedTypesInFuncCall, $callableParms[1]->name); } - if(isset($keyType) || isset($valueType)) { + if (isset($keyType) || isset($valueType)) { return $this->typeSpecifier->create( $array, new ArrayType($keyType ?? new MixedType(), $valueType ?? new MixedType()), $context, - $scope + $scope, ); } } @@ -116,17 +74,22 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n return new SpecifiedTypes(); } - // /** - // * @param array $paramTypes - // */ - // private function replaceVariable(Expr $expr, array $paramTypes) : Expr { - // if ($expr instanceof Variable) { - // $variableName = $expr->name; - // if (isset($paramTypes[$variableName])) { - // return $paramTypes[$variableName]; - // } - // } elseif ($expr instanceof ) - // } + /** + * @param array $specifiedTypes + */ + private function fetchTypeByVariable(array $specifiedTypes, string $variableName): ?Type + { + $specifiedTypeOfKey = array_find( + $specifiedTypes, + static fn ($specifiedType) => $specifiedType[0] instanceof Expr\Variable && $specifiedType[0]->name === $variableName, + ); + + if (isset($specifiedTypeOfKey)) { + return $specifiedTypeOfKey[1]; + } + + return null; + } public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void { diff --git a/tests/PHPStan/Analyser/TypeSpecifierTest.php b/tests/PHPStan/Analyser/TypeSpecifierTest.php index 228c76aed61..4297b4e9c4e 100644 --- a/tests/PHPStan/Analyser/TypeSpecifierTest.php +++ b/tests/PHPStan/Analyser/TypeSpecifierTest.php @@ -17,7 +17,6 @@ use PhpParser\Node\Name; use PhpParser\Node\Scalar\LNumber; use PhpParser\Node\Scalar\String_; -use PhpParser\Node\VariadicPlaceholder; use PhpParser\Node\VarLikeIdentifier; use PhpParser\PrettyPrinter\Standard; use PHPStan\Node\Expr\AlwaysRememberedExpr; @@ -27,7 +26,6 @@ use PHPStan\Type\ArrayType; use PHPStan\Type\ClassStringType; use PHPStan\Type\Constant\ConstantBooleanType; -use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\FloatType; use PHPStan\Type\Generic\GenericClassStringType; use PHPStan\Type\IntegerType; @@ -1324,136 +1322,136 @@ public static function dataCondition(): iterable ], [ new FuncCall( - new Name("array_all"), + new Name('array_all'), [ - new Arg(new Variable("array")), + new Arg(new Variable('array')), new Arg( new Expr\ArrowFunction( [ - 'expr' => new FuncCall(new Name("is_int"), [new Arg(new Variable("value"))]), - 'params' => [new Variable("value")], + 'expr' => new FuncCall(new Name('is_int'), [new Arg(new Variable('value'))]), + 'params' => [new Variable('value')], ], - [] - ) - ) - ] + [], + ), + ), + ], ), [ - '$array' => 'array' + '$array' => 'array', ], - [] + [], ], [ new FuncCall( - new Name("array_all"), + new Name('array_all'), [ - new Arg(new Variable("array")), + new Arg(new Variable('array')), new Arg( new Expr\ArrowFunction( [ - 'expr' => new FuncCall(new Name("is_string"), [new Arg(new Variable("key"))]), - 'params' => [new Variable("value"), new Variable("key")], + 'expr' => new FuncCall(new Name('is_string'), [new Arg(new Variable('key'))]), + 'params' => [new Variable('value'), new Variable('key')], ], - [] - ) - ) - ] + [], + ), + ), + ], ), [ - '$array' => 'array' + '$array' => 'array', ], - [] + [], ], [ new FuncCall( - new Name("array_all"), + new Name('array_all'), [ - new Arg(new Variable("array")), + new Arg(new Variable('array')), new Arg( new Expr\ArrowFunction( [ 'expr' => new Expr\BinaryOp\BooleanAnd( - new FuncCall(new Name("is_int"), [new Arg(new Variable("value"))]), - new FuncCall(new Name("is_string"), [new Arg(new Variable("key"))]), + new FuncCall(new Name('is_int'), [new Arg(new Variable('value'))]), + new FuncCall(new Name('is_string'), [new Arg(new Variable('key'))]), ), - 'params' => [new Variable("value"), new Variable("key")], + 'params' => [new Variable('value'), new Variable('key')], ], - [] - ) - ) - ] + [], + ), + ), + ], ), [ - '$array' => 'array' + '$array' => 'array', ], - [] + [], ], [ new FuncCall( - new Name("array_all"), + new Name('array_all'), [ - new Arg(new Variable("array")), + new Arg(new Variable('array')), new Arg( new Expr\ArrowFunction( [ 'expr' => new Expr\BinaryOp\BooleanAnd( - new FuncCall(new Name("is_string"), [new Arg(new Variable("value"))]), - new FuncCall(new Name("is_numeric"), [new Arg(new Variable("value"))]), + new FuncCall(new Name('is_string'), [new Arg(new Variable('value'))]), + new FuncCall(new Name('is_numeric'), [new Arg(new Variable('value'))]), ), - 'params' => [new Variable("value"), new Variable("key")], + 'params' => [new Variable('value'), new Variable('key')], ], - [] - ) - ) - ] + [], + ), + ), + ], ), [ - '$array' => 'array' + '$array' => 'array', ], - [] + [], ], [ new FuncCall( - new Name("array_all"), + new Name('array_all'), [ - new Arg(new Variable("array")), + new Arg(new Variable('array')), new Arg( new Expr\ArrowFunction( [ 'expr' => new Expr\BinaryOp\BooleanOr( - new FuncCall(new Name("is_float"), [new Arg(new Variable("value"))]), - new FuncCall(new Name("is_bool"), [new Arg(new Variable("value"))]), + new FuncCall(new Name('is_float'), [new Arg(new Variable('value'))]), + new FuncCall(new Name('is_bool'), [new Arg(new Variable('value'))]), ), - 'params' => [new Variable("value"), new Variable("key")], + 'params' => [new Variable('value'), new Variable('key')], ], - [] - ) - ) - ] + [], + ), + ), + ], ), [ - '$array' => 'array' + '$array' => 'array', ], - [] + [], ], [ new FuncCall( - new Name("array_all"), + new Name('array_all'), [ - new Arg(new Variable("array")), + new Arg(new Variable('array')), new Arg( new Expr\ArrowFunction( [ - 'expr' => new FuncCall(new Name("is_int"), [new Arg(new Expr\ConstFetch(new Name("1")))]), - 'params' => [new Variable("value")], + 'expr' => new FuncCall(new Name('is_int'), [new Arg(new Expr\ConstFetch(new Name('1')))]), + 'params' => [new Variable('value')], ], - [] - ) - ) - ] + [], + ), + ), + ], ), [], - [] + [], ], ]; } From f3caba93621f63b559e612b70312592a757f92a5 Mon Sep 17 00:00:00 2001 From: uekann Date: Mon, 9 Mar 2026 11:43:39 +0900 Subject: [PATCH 05/17] Add nsrt for array all function --- tests/PHPStan/Analyser/nsrt/array-all.php | 61 +++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/array-all.php diff --git a/tests/PHPStan/Analyser/nsrt/array-all.php b/tests/PHPStan/Analyser/nsrt/array-all.php new file mode 100644 index 00000000000..d66f882c27c --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/array-all.php @@ -0,0 +1,61 @@ += 8.4 + +namespace ArrayAll; + +use function PHPStan\Testing\assertType; + +class Foo { + /** + * @param array $array + */ + public function test1($array) { + if (array_all($array, fn ($value) => is_int($value))) { + assertType("array", $array); + } + } + + /** + * @param array $array + */ + public function test2($array) { + if (array_all($array, fn ($value, $key) => is_string($key))) { + assertType("array", $array); + } + } + + /** + * @param array $array + */ + public function test3($array) { + if (array_all($array, fn ($value, $key) => is_string($key) && is_int($value))) { + assertType("array", $array); + } + } + + /** + * @param array $array + */ + public function test4($array) { + if (array_all($array, fn ($value) => is_string($value) && is_numeric($value))) { + assertType("array", $array); + } + } + + /** + * @param array $array + */ + public function test5($array) { + if (array_all($array, fn ($value) => is_bool($value) || is_float($value))) { + assertType("array", $array); + } + } + + /** + * @param array $array + */ + public function test6($array) { + if (array_all($array, fn ($value) => is_float(1))) { + assertType("array", $array); + } + } +} From da079f8155d7b06dc927502801eac44983b0e7d9 Mon Sep 17 00:00:00 2001 From: uekann Date: Mon, 9 Mar 2026 11:58:33 +0900 Subject: [PATCH 06/17] Fix condition --- ...ArrayAllFunctionTypeSpecifyingExtension.php | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php b/src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php index eb6fd453374..a92ac21d51d 100644 --- a/src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php @@ -2,8 +2,10 @@ namespace PHPStan\Type\Php; +use PhpParser\Node\Param; use PhpParser\Node\Expr; use PhpParser\Node\Expr\FuncCall; +use PhpParser\Node\Expr\Variable; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; use PHPStan\Analyser\TypeSpecifier; @@ -53,12 +55,20 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n $callableParms = $callable->params; $specifiedTypesInFuncCall = $this->typeSpecifier->specifyTypesInCondition($scope, $callable->expr, $context)->getSureTypes(); - if (isset($callableParms[0]) && $callableParms[0] instanceof Expr\Variable) { - $valueType = $this->fetchTypeByVariable($specifiedTypesInFuncCall, $callableParms[0]->name); + if ( + isset($callableParms[0]) && + $callableParms[0] instanceof Param && + $callableParms[0]->var instanceof Variable + ) { + $valueType = $this->fetchTypeByVariable($specifiedTypesInFuncCall, $callableParms[0]->var->name); } - if (isset($callableParms[1]) && $callableParms[1] instanceof Expr\Variable) { - $keyType = $this->fetchTypeByVariable($specifiedTypesInFuncCall, $callableParms[1]->name); + if ( + isset($callableParms[1]) && + $callableParms[1] instanceof Param && + $callableParms[1]->var instanceof Variable + ) { + $keyType = $this->fetchTypeByVariable($specifiedTypesInFuncCall, $callableParms[1]->var->name); } if (isset($keyType) || isset($valueType)) { From 1e8072ab819705e29c43a2b2fadc3b3cc6a3e839 Mon Sep 17 00:00:00 2001 From: uekann Date: Mon, 9 Mar 2026 12:02:19 +0900 Subject: [PATCH 07/17] Remove unnecessary tests --- tests/PHPStan/Analyser/TypeSpecifierTest.php | 134 +------------------ 1 file changed, 1 insertion(+), 133 deletions(-) diff --git a/tests/PHPStan/Analyser/TypeSpecifierTest.php b/tests/PHPStan/Analyser/TypeSpecifierTest.php index 4297b4e9c4e..a9ec9ef62eb 100644 --- a/tests/PHPStan/Analyser/TypeSpecifierTest.php +++ b/tests/PHPStan/Analyser/TypeSpecifierTest.php @@ -2,6 +2,7 @@ namespace PHPStan\Analyser; +use Bug4820\Param; use Override; use PhpParser\Node\Arg; use PhpParser\Node\Expr; @@ -1320,139 +1321,6 @@ public static function dataCondition(): iterable ], [], ], - [ - new FuncCall( - new Name('array_all'), - [ - new Arg(new Variable('array')), - new Arg( - new Expr\ArrowFunction( - [ - 'expr' => new FuncCall(new Name('is_int'), [new Arg(new Variable('value'))]), - 'params' => [new Variable('value')], - ], - [], - ), - ), - ], - ), - [ - '$array' => 'array', - ], - [], - ], - [ - new FuncCall( - new Name('array_all'), - [ - new Arg(new Variable('array')), - new Arg( - new Expr\ArrowFunction( - [ - 'expr' => new FuncCall(new Name('is_string'), [new Arg(new Variable('key'))]), - 'params' => [new Variable('value'), new Variable('key')], - ], - [], - ), - ), - ], - ), - [ - '$array' => 'array', - ], - [], - ], - [ - new FuncCall( - new Name('array_all'), - [ - new Arg(new Variable('array')), - new Arg( - new Expr\ArrowFunction( - [ - 'expr' => new Expr\BinaryOp\BooleanAnd( - new FuncCall(new Name('is_int'), [new Arg(new Variable('value'))]), - new FuncCall(new Name('is_string'), [new Arg(new Variable('key'))]), - ), - 'params' => [new Variable('value'), new Variable('key')], - ], - [], - ), - ), - ], - ), - [ - '$array' => 'array', - ], - [], - ], - [ - new FuncCall( - new Name('array_all'), - [ - new Arg(new Variable('array')), - new Arg( - new Expr\ArrowFunction( - [ - 'expr' => new Expr\BinaryOp\BooleanAnd( - new FuncCall(new Name('is_string'), [new Arg(new Variable('value'))]), - new FuncCall(new Name('is_numeric'), [new Arg(new Variable('value'))]), - ), - 'params' => [new Variable('value'), new Variable('key')], - ], - [], - ), - ), - ], - ), - [ - '$array' => 'array', - ], - [], - ], - [ - new FuncCall( - new Name('array_all'), - [ - new Arg(new Variable('array')), - new Arg( - new Expr\ArrowFunction( - [ - 'expr' => new Expr\BinaryOp\BooleanOr( - new FuncCall(new Name('is_float'), [new Arg(new Variable('value'))]), - new FuncCall(new Name('is_bool'), [new Arg(new Variable('value'))]), - ), - 'params' => [new Variable('value'), new Variable('key')], - ], - [], - ), - ), - ], - ), - [ - '$array' => 'array', - ], - [], - ], - [ - new FuncCall( - new Name('array_all'), - [ - new Arg(new Variable('array')), - new Arg( - new Expr\ArrowFunction( - [ - 'expr' => new FuncCall(new Name('is_int'), [new Arg(new Expr\ConstFetch(new Name('1')))]), - 'params' => [new Variable('value')], - ], - [], - ), - ), - ], - ), - [], - [], - ], ]; } From 892da770f947d79aad826434016f5bd14f5fdb01 Mon Sep 17 00:00:00 2001 From: uekann Date: Mon, 9 Mar 2026 12:03:37 +0900 Subject: [PATCH 08/17] Refactor --- ...rrayAllFunctionTypeSpecifyingExtension.php | 20 +++++++++---------- tests/PHPStan/Analyser/TypeSpecifierTest.php | 1 - 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php b/src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php index a92ac21d51d..900cc22dc71 100644 --- a/src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php @@ -2,10 +2,10 @@ namespace PHPStan\Type\Php; -use PhpParser\Node\Param; use PhpParser\Node\Expr; use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Expr\Variable; +use PhpParser\Node\Param; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; use PHPStan\Analyser\TypeSpecifier; @@ -52,23 +52,23 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n if ($callable instanceof Expr\ArrowFunction) { - $callableParms = $callable->params; + $callableParams = $callable->params; $specifiedTypesInFuncCall = $this->typeSpecifier->specifyTypesInCondition($scope, $callable->expr, $context)->getSureTypes(); if ( - isset($callableParms[0]) && - $callableParms[0] instanceof Param && - $callableParms[0]->var instanceof Variable + isset($callableParams[0]) && + $callableParams[0]->var instanceof Variable && + is_string($callableParams[0]->var->name) ) { - $valueType = $this->fetchTypeByVariable($specifiedTypesInFuncCall, $callableParms[0]->var->name); + $valueType = $this->fetchTypeByVariable($specifiedTypesInFuncCall, $callableParams[0]->var->name); } if ( - isset($callableParms[1]) && - $callableParms[1] instanceof Param && - $callableParms[1]->var instanceof Variable + isset($callableParams[1]) && + $callableParams[1]->var instanceof Variable && + is_string($callableParams[1]->var->name) ) { - $keyType = $this->fetchTypeByVariable($specifiedTypesInFuncCall, $callableParms[1]->var->name); + $keyType = $this->fetchTypeByVariable($specifiedTypesInFuncCall, $callableParams[1]->var->name); } if (isset($keyType) || isset($valueType)) { diff --git a/tests/PHPStan/Analyser/TypeSpecifierTest.php b/tests/PHPStan/Analyser/TypeSpecifierTest.php index a9ec9ef62eb..aa66c940016 100644 --- a/tests/PHPStan/Analyser/TypeSpecifierTest.php +++ b/tests/PHPStan/Analyser/TypeSpecifierTest.php @@ -2,7 +2,6 @@ namespace PHPStan\Analyser; -use Bug4820\Param; use Override; use PhpParser\Node\Arg; use PhpParser\Node\Expr; From 1683345c127536dc3c6b17e6b39023b330709664 Mon Sep 17 00:00:00 2001 From: uekann Date: Mon, 9 Mar 2026 13:40:33 +0900 Subject: [PATCH 09/17] Add new test case for array_all --- tests/PHPStan/Analyser/nsrt/array-all.php | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/PHPStan/Analyser/nsrt/array-all.php b/tests/PHPStan/Analyser/nsrt/array-all.php index d66f882c27c..6c63de02a1e 100644 --- a/tests/PHPStan/Analyser/nsrt/array-all.php +++ b/tests/PHPStan/Analyser/nsrt/array-all.php @@ -2,6 +2,9 @@ namespace ArrayAll; +use DateTime; +use DateTimeImmutable; + use function PHPStan\Testing\assertType; class Foo { @@ -58,4 +61,22 @@ public function test6($array) { assertType("array", $array); } } + + /** + * @param array $array + */ + public function test7($array) { + if (array_all($array, fn ($value) => $value instanceof DateTime)) { + assertType("array", $array); + } + } + + /** + * @param array $array + */ + public function test8($array) { + if (array_all($array, fn ($value) => $value instanceof DateTime || $value instanceof DateTimeImmutable)) { + assertType("array", $array); + } + } } From f375cbd163b5e34c1cae591f96ec927ff7d335ba Mon Sep 17 00:00:00 2001 From: uekann Date: Mon, 9 Mar 2026 14:15:10 +0900 Subject: [PATCH 10/17] Fix cs error --- src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php b/src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php index 900cc22dc71..eaf21aee830 100644 --- a/src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php @@ -5,7 +5,6 @@ use PhpParser\Node\Expr; use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Expr\Variable; -use PhpParser\Node\Param; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; use PHPStan\Analyser\TypeSpecifier; @@ -19,6 +18,7 @@ use PHPStan\Type\Type; use function array_find; use function count; +use function is_string; use function strtolower; #[AutowiredService] From 1a78e37ad72a425137ed3c920fd87eafac52c997 Mon Sep 17 00:00:00 2001 From: uekann Date: Tue, 10 Mar 2026 19:09:26 +0900 Subject: [PATCH 11/17] Add test case for Closure, list and non-empty-array --- tests/PHPStan/Analyser/nsrt/array-all.php | 86 +++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/tests/PHPStan/Analyser/nsrt/array-all.php b/tests/PHPStan/Analyser/nsrt/array-all.php index 6c63de02a1e..3bc7989c245 100644 --- a/tests/PHPStan/Analyser/nsrt/array-all.php +++ b/tests/PHPStan/Analyser/nsrt/array-all.php @@ -8,13 +8,17 @@ use function PHPStan\Testing\assertType; class Foo { + /** * @param array $array */ public function test1($array) { if (array_all($array, fn ($value) => is_int($value))) { assertType("array", $array); + } else { + assertType("array", $array); } + assertType("array", $array); } /** @@ -23,7 +27,10 @@ public function test1($array) { public function test2($array) { if (array_all($array, fn ($value, $key) => is_string($key))) { assertType("array", $array); + } else { + assertType("array", $array); } + assertType("array", $array); } /** @@ -32,7 +39,10 @@ public function test2($array) { public function test3($array) { if (array_all($array, fn ($value, $key) => is_string($key) && is_int($value))) { assertType("array", $array); + } else { + assertType("array", $array); } + assertType("array", $array); } /** @@ -41,7 +51,10 @@ public function test3($array) { public function test4($array) { if (array_all($array, fn ($value) => is_string($value) && is_numeric($value))) { assertType("array", $array); + } else { + assertType("array", $array); } + assertType("array", $array); } /** @@ -50,7 +63,10 @@ public function test4($array) { public function test5($array) { if (array_all($array, fn ($value) => is_bool($value) || is_float($value))) { assertType("array", $array); + } else { + assertType("array", $array); } + assertType("array", $array); } /** @@ -59,7 +75,10 @@ public function test5($array) { public function test6($array) { if (array_all($array, fn ($value) => is_float(1))) { assertType("array", $array); + } else { + assertType("array", $array); } + assertType("array", $array); } /** @@ -68,7 +87,10 @@ public function test6($array) { public function test7($array) { if (array_all($array, fn ($value) => $value instanceof DateTime)) { assertType("array", $array); + } else { + assertType("array", $array); } + assertType("array", $array); } /** @@ -77,6 +99,70 @@ public function test7($array) { public function test8($array) { if (array_all($array, fn ($value) => $value instanceof DateTime || $value instanceof DateTimeImmutable)) { assertType("array", $array); + } else { + assertType("array", $array); + } + assertType("array", $array); + } + + /** + * @param list $array + */ + public function test9($array) { + if (array_all($array, fn ($value, $key) => is_int($key))) { + assertType("list", $array); + } else { + assertType("list", $array); + } + assertType("list", $array); + } + + /** + * @param non-empty-array $array + */ + public function test10($array) { + if (array_all($array, fn ($value, $key) => is_int($key))) { + assertType("non-empty-array", $array); + } else { + assertType("non-empty-array", $array); + } + assertType("non-empty-array", $array); + } + + /** + * @param array $array + */ + public function test11($array) { + if (array_all($array, function ($value) {return is_int($value);})) { + assertType("array", $array); + } else { + assertType("array", $array); + } + assertType("array", $array); + } + + /** + * @param array $array + */ + public function test12($array) { + if (array_all($array, function ($value) {$value = 1; return is_int($value);})) { + assertType("array", $array); + } else { + assertType("array", $array); } + assertType("array", $array); } + + /** + * @param array $array + */ + public function test13($array) { + if (array_all($array, function ($value, $key) {return is_int($value) && is_string($key);})) { + assertType("array", $array); + } else { + assertType("array", $array); + } + assertType("array", $array); + } + } From ef20fa9a67e6c8d67d2c8b4fc48f0a30fae15c17 Mon Sep 17 00:00:00 2001 From: uekann Date: Tue, 10 Mar 2026 19:12:00 +0900 Subject: [PATCH 12/17] Support for closures containing only return statements --- ...rrayAllFunctionTypeSpecifyingExtension.php | 68 ++++++++++--------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php b/src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php index eaf21aee830..59af6f8f835 100644 --- a/src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php @@ -5,6 +5,7 @@ use PhpParser\Node\Expr; use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Expr\Variable; +use PhpParser\Node\Stmt; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; use PHPStan\Analyser\TypeSpecifier; @@ -41,44 +42,45 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n } $array = $args[0]->value; - $arrayArgType = $scope->getType($array); - $arrayTypes = $arrayArgType->getArrays(); - - if (count($arrayTypes) === 0) { + $callable = $args[1]->value; + if ($callable instanceof Expr\ArrowFunction) { + $callableExpr = $callable->expr; + } elseif ( + $callable instanceof Expr\Closure && + count($callable->stmts) === 1 && + $callable->stmts[0] instanceof Stmt\Return_ + ) { + $callableExpr = $callable->stmts[0]->expr; + } else { return new SpecifiedTypes(); } - $callable = $args[1]->value; + $callableParams = $callable->params; + $specifiedTypesInFuncCall = $this->typeSpecifier->specifyTypesInCondition($scope, $callableExpr, $context)->getSureTypes(); - if ($callable instanceof Expr\ArrowFunction) { + if ( + isset($callableParams[0]) && + $callableParams[0]->var instanceof Variable && + is_string($callableParams[0]->var->name) + ) { + $valueType = $this->fetchTypeByVariable($specifiedTypesInFuncCall, $callableParams[0]->var->name); + } + + if ( + isset($callableParams[1]) && + $callableParams[1]->var instanceof Variable && + is_string($callableParams[1]->var->name) + ) { + $keyType = $this->fetchTypeByVariable($specifiedTypesInFuncCall, $callableParams[1]->var->name); + } - $callableParams = $callable->params; - $specifiedTypesInFuncCall = $this->typeSpecifier->specifyTypesInCondition($scope, $callable->expr, $context)->getSureTypes(); - - if ( - isset($callableParams[0]) && - $callableParams[0]->var instanceof Variable && - is_string($callableParams[0]->var->name) - ) { - $valueType = $this->fetchTypeByVariable($specifiedTypesInFuncCall, $callableParams[0]->var->name); - } - - if ( - isset($callableParams[1]) && - $callableParams[1]->var instanceof Variable && - is_string($callableParams[1]->var->name) - ) { - $keyType = $this->fetchTypeByVariable($specifiedTypesInFuncCall, $callableParams[1]->var->name); - } - - if (isset($keyType) || isset($valueType)) { - return $this->typeSpecifier->create( - $array, - new ArrayType($keyType ?? new MixedType(), $valueType ?? new MixedType()), - $context, - $scope, - ); - } + if (isset($keyType) || isset($valueType)) { + return $this->typeSpecifier->create( + $array, + new ArrayType($keyType ?? new MixedType(), $valueType ?? new MixedType()), + $context, + $scope, + ); } return new SpecifiedTypes(); From ce447876b655b41e170ec1970e4905949e30e4e1 Mon Sep 17 00:00:00 2001 From: uekann Date: Wed, 11 Mar 2026 10:35:28 +0900 Subject: [PATCH 13/17] Fix phpstan error --- src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php b/src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php index 59af6f8f835..c7294d61538 100644 --- a/src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php @@ -55,6 +55,8 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n return new SpecifiedTypes(); } + assert(isset($callableExpr)); + $callableParams = $callable->params; $specifiedTypesInFuncCall = $this->typeSpecifier->specifyTypesInCondition($scope, $callableExpr, $context)->getSureTypes(); From 1464a9f05b9a6bf35729562dc37b91c7fd422469 Mon Sep 17 00:00:00 2001 From: uekann Date: Wed, 11 Mar 2026 10:38:56 +0900 Subject: [PATCH 14/17] Fix cs error --- src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php b/src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php index c7294d61538..d2c4126f863 100644 --- a/src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php @@ -18,6 +18,7 @@ use PHPStan\Type\MixedType; use PHPStan\Type\Type; use function array_find; +use function assert; use function count; use function is_string; use function strtolower; From d0fe0803977fced6259c19e4aaa709e856133028 Mon Sep 17 00:00:00 2001 From: uekann Date: Wed, 11 Mar 2026 12:03:40 +0900 Subject: [PATCH 15/17] Add a test case for a closure whose return expression is null --- tests/PHPStan/Analyser/nsrt/array-all.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/PHPStan/Analyser/nsrt/array-all.php b/tests/PHPStan/Analyser/nsrt/array-all.php index 3bc7989c245..b2903e9865c 100644 --- a/tests/PHPStan/Analyser/nsrt/array-all.php +++ b/tests/PHPStan/Analyser/nsrt/array-all.php @@ -165,4 +165,15 @@ public function test13($array) { assertType("array", $array); } + /** + * @param array $array + */ + public function test14($array) { + if (array_all($array, function ($value, $key) {return;})) { + assertType("array", $array); + } else { + assertType("array", $array); + } + assertType("array", $array); + } } From e8d86be7f0a7adb83fe673230428c33ec7a2d39f Mon Sep 17 00:00:00 2001 From: uekann Date: Wed, 11 Mar 2026 12:09:33 +0900 Subject: [PATCH 16/17] Refactor --- src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php b/src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php index d2c4126f863..87dd6811582 100644 --- a/src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php @@ -18,7 +18,6 @@ use PHPStan\Type\MixedType; use PHPStan\Type\Type; use function array_find; -use function assert; use function count; use function is_string; use function strtolower; @@ -38,7 +37,7 @@ public function isFunctionSupported(FunctionReflection $functionReflection, Func public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes { $args = $node->getArgs(); - if (!$context->true() || count($args) < 2) { + if (!$context->truthy() || count($args) < 2) { return new SpecifiedTypes(); } @@ -49,15 +48,14 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n } elseif ( $callable instanceof Expr\Closure && count($callable->stmts) === 1 && - $callable->stmts[0] instanceof Stmt\Return_ + $callable->stmts[0] instanceof Stmt\Return_ && + isset($callable->stmts[0]->expr) ) { $callableExpr = $callable->stmts[0]->expr; } else { return new SpecifiedTypes(); } - assert(isset($callableExpr)); - $callableParams = $callable->params; $specifiedTypesInFuncCall = $this->typeSpecifier->specifyTypesInCondition($scope, $callableExpr, $context)->getSureTypes(); From 14e0f3dd7aaac6500a1d0e435b86e5cc912b0983 Mon Sep 17 00:00:00 2001 From: uekann Date: Fri, 13 Mar 2026 00:28:13 +0900 Subject: [PATCH 17/17] Add a test case where the context is truthy --- tests/PHPStan/Analyser/nsrt/array-all.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/PHPStan/Analyser/nsrt/array-all.php b/tests/PHPStan/Analyser/nsrt/array-all.php index b2903e9865c..8ec15008056 100644 --- a/tests/PHPStan/Analyser/nsrt/array-all.php +++ b/tests/PHPStan/Analyser/nsrt/array-all.php @@ -176,4 +176,16 @@ public function test14($array) { } assertType("array", $array); } + + /** + * @param array $array + */ + public function test15($array) { + if (array_all($array, fn ($value) => is_int($value)) === true) { + assertType("array", $array); + } else { + assertType("array", $array); + } + assertType("array", $array); + } }