Skip to content

Commit

Permalink
Fix array_reverse type for list{0: int, 1?: int} etc.
Browse files Browse the repository at this point in the history
  • Loading branch information
MoonE committed Nov 6, 2024
1 parent 0f796f1 commit c297b4c
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Psalm\Internal\Provider\ReturnTypeProvider;

use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\Internal\Type\TypeCombiner;
use Psalm\Plugin\EventHandler\Event\FunctionReturnTypeProviderEvent;
use Psalm\Plugin\EventHandler\FunctionReturnTypeProviderInterface;
use Psalm\Type;
Expand All @@ -11,6 +12,8 @@
use Psalm\Type\Union;

use function array_reverse;
use function array_values;
use function count;

/**
* @internal
Expand Down Expand Up @@ -57,17 +60,46 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev
if ($first_arg_array->is_list) {
$second_arg = $call_args[1]->value ?? null;

if (!$second_arg
|| (($second_arg_type = $statements_source->node_data->getType($second_arg))
&& $second_arg_type->isFalse()
)
) {
return $first_arg_array->fallback_params
? ($first_arg_array->isNonEmpty()
$preserve_keys =
$second_arg &&
!(($second_arg_type = $statements_source->node_data->getType($second_arg)) &&
$second_arg_type->isFalse());
if (!$preserve_keys) {
if ($first_arg_array->fallback_params) {
return $first_arg_array->isNonEmpty()
? Type::getNonEmptyList($first_arg_array->getGenericValueType())
: Type::getList($first_arg_array->getGenericValueType())
)
: new Union([$first_arg_array->setProperties(array_reverse($first_arg_array->properties))]);
: Type::getList($first_arg_array->getGenericValueType());
}

$reversed_array_items = [];
$num_undefined = 0;
$i = 0;
foreach (array_reverse($first_arg_array->properties) as $array_item_type) {
$reversed_array_items[] = $array_item_type;
/** @var int<0,max> $j */
$j = $i - $num_undefined;
for (; $j < $i; ++$j) {
$reversed_array_items[$j] = TypeCombiner::combine([
...array_values($reversed_array_items[$j]->getAtomicTypes()),
...array_values($array_item_type->getAtomicTypes()),
]);
}
if ($array_item_type->possibly_undefined) {
++$num_undefined;
}
++$i;
}

$max_len = count($reversed_array_items);
/** @var int<0,max> $i */
$i = $max_len - $num_undefined;
for (; $i < $max_len; ++$i) {
$reversed_array_items[$i] = $reversed_array_items[$i]->setPossiblyUndefined(
$i >= $max_len - $num_undefined,
);
}

return new Union([$first_arg_array->setProperties($reversed_array_items)]);
}

return new Union([new TKeyedArray(
Expand Down
25 changes: 25 additions & 0 deletions tests/ArrayFunctionCallTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,31 @@ public function replace($a, $b): array
'$d' => 'non-empty-array<int|string, int|string>',
],
],
'arrayReverseListDontPreserveKey' => [
'code' => '<?php
/** @return list{0: int, 1: float, 2: int, 3: false, 4?: string|true, 5?: true} */
function f(): array {
return [1, 1.1, 2, false, "", true];
}
/** @return list{0: int, 1: int, 2: int, 3?: int, 4?: int} */
function g(): array { return [1,2,3]; }
$r = array_reverse(f());
$s = array_reverse(g());',
'assertions' => [
'$r' => 'list{0: bool|string, 1: bool|int|string, 2: false|float|int, 3: float|int, 4?: float|int, 5?: int}',
'$s' => 'list{0: int, 1: int, 2: int, 3?: int, 4?: int}',
],
],
'arrayReverseListInt' => [
'code' => '<?php
/** @return list<int> */
function f(): array { return []; }
$a = array_reverse(f());',
'assertions' => [
'$a' => 'list<int>',
],
],
'arrayReverseDontPreserveKeyExplicitArg' => [
'code' => '<?php
$d = array_reverse(["a", "b", 1, "d" => 4], false);',
Expand Down

0 comments on commit c297b4c

Please sign in to comment.