diff --git a/src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php b/src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php new file mode 100644 index 00000000000..87dd6811582 --- /dev/null +++ b/src/Type/Php/ArrayAllFunctionTypeSpecifyingExtension.php @@ -0,0 +1,112 @@ +getName()) === 'array_all' + && !$context->null(); + } + + public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes + { + $args = $node->getArgs(); + if (!$context->truthy() || count($args) < 2) { + return new SpecifiedTypes(); + } + + $array = $args[0]->value; + $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_ && + isset($callable->stmts[0]->expr) + ) { + $callableExpr = $callable->stmts[0]->expr; + } else { + return new SpecifiedTypes(); + } + + $callableParams = $callable->params; + $specifiedTypesInFuncCall = $this->typeSpecifier->specifyTypesInCondition($scope, $callableExpr, $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, + ); + } + + return new SpecifiedTypes(); + } + + /** + * @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 + { + $this->typeSpecifier = $typeSpecifier; + } + +} diff --git a/tests/PHPStan/Analyser/nsrt/array-all.php b/tests/PHPStan/Analyser/nsrt/array-all.php new file mode 100644 index 00000000000..8ec15008056 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/array-all.php @@ -0,0 +1,191 @@ += 8.4 + +namespace ArrayAll; + +use DateTime; +use DateTimeImmutable; + +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); + } + + /** + * @param array $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); + } + + /** + * @param array $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); + } + + /** + * @param array $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); + } + + /** + * @param array $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); + } + + /** + * @param array $array + */ + public function test6($array) { + if (array_all($array, fn ($value) => is_float(1))) { + assertType("array", $array); + } else { + assertType("array", $array); + } + assertType("array", $array); + } + + /** + * @param array $array + */ + public function test7($array) { + if (array_all($array, fn ($value) => $value instanceof DateTime)) { + assertType("array", $array); + } else { + assertType("array", $array); + } + 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); + } 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); + } + + /** + * @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); + } + + /** + * @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); + } +}