From fd9ddb5fac14087080d056d0f6daaef11177cd40 Mon Sep 17 00:00:00 2001 From: schlndh Date: Fri, 11 Oct 2024 12:42:16 +0200 Subject: [PATCH 1/2] add null to array_map(null, $a, $b) --- .../ArrayMapFunctionReturnTypeExtension.php | 36 +++++++++++++++++-- .../Analyser/nsrt/array_map_multiple.php | 4 +-- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php b/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php index e8e9e3a457..50094cc573 100644 --- a/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php @@ -32,7 +32,8 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type { - if (count($functionCall->getArgs()) < 2) { + $numArgs = count($functionCall->getArgs()); + if ($numArgs < 2) { return null; } @@ -54,10 +55,41 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, )->getReturnType(); } elseif ($callableIsNull) { $arrayBuilder = ConstantArrayTypeBuilder::createEmpty(); + $argIterableValueTypes = []; + $addNull = false; + $expectedSize = null; foreach (array_slice($functionCall->getArgs(), 1) as $index => $arg) { + $argType = $scope->getType($arg->value); + $argIterableValueTypes[$index] = $argType->getIterableValueType(); + if ($addNull) { + continue; + } + + $arraySizes = $argType->getArraySize()->getConstantScalarValues(); + if ($arraySizes === []) { + $addNull = true; + continue; + } + + foreach ($arraySizes as $size) { + $expectedSize ??= $size; + if ($expectedSize === $size) { + continue; + } + + $addNull = true; + break; + } + } + + foreach ($argIterableValueTypes as $index => $offsetValueType) { + if ($addNull) { + $offsetValueType = TypeCombinator::addNull($offsetValueType); + } + $arrayBuilder->setOffsetValueType( new ConstantIntegerType($index), - $scope->getType($arg->value)->getIterableValueType(), + $offsetValueType, ); } $valueType = $arrayBuilder->getArray(); diff --git a/tests/PHPStan/Analyser/nsrt/array_map_multiple.php b/tests/PHPStan/Analyser/nsrt/array_map_multiple.php index ce73048a46..2e05c6160d 100644 --- a/tests/PHPStan/Analyser/nsrt/array_map_multiple.php +++ b/tests/PHPStan/Analyser/nsrt/array_map_multiple.php @@ -29,8 +29,8 @@ public function arrayMapNull(array $array, array $other): void assertType('non-empty-array', array_map(null, [1, 2, 3], [4, 5, 6])); assertType('non-empty-array', array_map(null, $array)); - assertType('non-empty-array', array_map(null, $array, $array)); - assertType('non-empty-array', array_map(null, $array, $other)); + assertType('non-empty-array', array_map(null, $array, $array)); + assertType('non-empty-array', array_map(null, $array, $other)); } } From 9f7938567debaf4d65ecf15cc78f814834ad7d1a Mon Sep 17 00:00:00 2001 From: schlndh Date: Fri, 11 Oct 2024 13:01:26 +0200 Subject: [PATCH 2/2] don't add null for array_map(null, $a, $a) --- .../ArrayMapFunctionReturnTypeExtension.php | 36 ++++++++++++++----- .../Analyser/nsrt/array_map_multiple.php | 6 +++- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php b/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php index 50094cc573..ce29b37d2a 100644 --- a/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php @@ -19,6 +19,7 @@ use PHPStan\Type\TypeCombinator; use PHPStan\Type\TypeUtils; use function array_map; +use function array_reduce; use function array_slice; use function count; @@ -55,19 +56,18 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, )->getReturnType(); } elseif ($callableIsNull) { $arrayBuilder = ConstantArrayTypeBuilder::createEmpty(); - $argIterableValueTypes = []; - $addNull = false; + $argTypes = []; + $areAllSameSize = true; $expectedSize = null; foreach (array_slice($functionCall->getArgs(), 1) as $index => $arg) { - $argType = $scope->getType($arg->value); - $argIterableValueTypes[$index] = $argType->getIterableValueType(); - if ($addNull) { + $argTypes[$index] = $argType = $scope->getType($arg->value); + if (!$areAllSameSize || $numArgs === 2) { continue; } $arraySizes = $argType->getArraySize()->getConstantScalarValues(); if ($arraySizes === []) { - $addNull = true; + $areAllSameSize = false; continue; } @@ -77,12 +77,30 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, continue; } - $addNull = true; - break; + $areAllSameSize = false; + continue 2; } } - foreach ($argIterableValueTypes as $index => $offsetValueType) { + if (!$areAllSameSize) { + $firstArr = $functionCall->getArgs()[1]->value; + $identities = []; + foreach (array_slice($functionCall->getArgs(), 2) as $arg) { + $identities[] = new Node\Expr\BinaryOp\Identical($firstArr, $arg->value); + } + + $and = array_reduce( + $identities, + static fn (Node\Expr $a, Node\Expr $b) => new Node\Expr\BinaryOp\BooleanAnd($a, $b), + new Node\Expr\ConstFetch(new Node\Name('true')), + ); + $areAllSameSize = $scope->getType($and)->isTrue()->yes(); + } + + $addNull = !$areAllSameSize; + + foreach ($argTypes as $index => $argType) { + $offsetValueType = $argType->getIterableValueType(); if ($addNull) { $offsetValueType = TypeCombinator::addNull($offsetValueType); } diff --git a/tests/PHPStan/Analyser/nsrt/array_map_multiple.php b/tests/PHPStan/Analyser/nsrt/array_map_multiple.php index 2e05c6160d..d986969c3e 100644 --- a/tests/PHPStan/Analyser/nsrt/array_map_multiple.php +++ b/tests/PHPStan/Analyser/nsrt/array_map_multiple.php @@ -29,8 +29,12 @@ public function arrayMapNull(array $array, array $other): void assertType('non-empty-array', array_map(null, [1, 2, 3], [4, 5, 6])); assertType('non-empty-array', array_map(null, $array)); - assertType('non-empty-array', array_map(null, $array, $array)); + assertType('non-empty-array', array_map(null, $array, $array)); + assertType('non-empty-array', array_map(null, $array, $array, $array)); assertType('non-empty-array', array_map(null, $array, $other)); + + assertType('array{1}|array{true}', array_map(null, rand() ? [1] : [true])); + assertType('array{1}|array{true, false}', array_map(null, rand() ? [1] : [true, false])); } }