1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-26 20:34:47 +01:00

Merge pull request #9643 from whizsid/array_splice

Fixed non empty arg issue in array_splice function
This commit is contained in:
orklah 2023-04-13 19:24:16 +02:00 committed by GitHub
commit cee88f2c4b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 216 additions and 38 deletions

View File

@ -14,6 +14,7 @@ use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\Internal\Codebase\InternalCallMapHandler;
use Psalm\Internal\MethodIdentifier;
use Psalm\Internal\Type\ArrayType;
use Psalm\Internal\Type\Comparator\TypeComparisonResult;
use Psalm\Internal\Type\Comparator\UnionTypeComparator;
use Psalm\Internal\Type\TemplateResult;
@ -47,6 +48,7 @@ use function array_unshift;
use function assert;
use function count;
use function explode;
use function is_numeric;
use function strpos;
use function strtolower;
use function substr;
@ -346,6 +348,40 @@ class ArrayFunctionArgumentsAnalyzer
return false;
}
$array_type = null;
$array_size = null;
if (($array_arg_type = $statements_analyzer->node_data->getType($array_arg))
&& $array_arg_type->hasArray()
) {
/**
* @var TArray|TKeyedArray
*/
$array_type = $array_arg_type->getArray();
if ($generic_array_type = ArrayType::infer($array_type)) {
$array_size = $generic_array_type->count;
}
if ($array_type instanceof TKeyedArray) {
if ($array_type->is_list && isset($args[3])) {
$array_type = Type::getNonEmptyListAtomic($array_type->getGenericValueType());
} else {
$array_type = $array_type->getGenericArrayType();
}
}
if ($array_type instanceof TArray
&& $array_type->type_params[0]->hasInt()
&& !$array_type->type_params[0]->hasString()
) {
if ($array_type instanceof TNonEmptyArray && isset($args[3])) {
$array_type = Type::getNonEmptyListAtomic($array_type->type_params[1]);
} else {
$array_type = Type::getListAtomic($array_type->type_params[1]);
}
}
}
$offset_arg = $args[1]->value;
if (ExpressionAnalyzer::analyze(
@ -356,7 +392,47 @@ class ArrayFunctionArgumentsAnalyzer
return false;
}
$offset_arg_is_zero = false;
if (($offset_arg_type = $statements_analyzer->node_data->getType($offset_arg))
&& $offset_arg_type->hasLiteralValue()
) {
$offset_literal_value = $offset_arg_type->getSingleLiteral()->value;
$offset_arg_is_zero = is_numeric($offset_literal_value) && ((int) $offset_literal_value)===0;
}
if (!isset($args[2])) {
if ($offset_arg_is_zero) {
$array_type = Type::getEmptyArray();
AssignmentAnalyzer::assignByRefParam(
$statements_analyzer,
$array_arg,
$array_type,
$array_type,
$context,
false,
);
} elseif ($array_type) {
AssignmentAnalyzer::assignByRefParam(
$statements_analyzer,
$array_arg,
new Union([$array_type]),
new Union([$array_type]),
$context,
false,
);
} else {
$default_array_type = Type::getArray();
AssignmentAnalyzer::assignByRefParam(
$statements_analyzer,
$array_arg,
$default_array_type,
$default_array_type,
$context,
false,
);
}
return null;
}
@ -370,7 +446,55 @@ class ArrayFunctionArgumentsAnalyzer
return false;
}
$cover_whole_arr = false;
if ($offset_arg_is_zero && is_numeric($array_size)) {
if (($length_arg_type = $statements_analyzer->node_data->getType($length_arg))
&& $length_arg_type->hasLiteralValue()
) {
$length_literal = $length_arg_type->getSingleLiteral();
if ($length_literal->isNumericType()) {
$length_value = (int)$length_literal->value;
if ($length_value>=$array_size) {
$cover_whole_arr = true;
}
}
} elseif ($length_arg_type&& $length_arg_type->isNull()) {
$cover_whole_arr = true;
}
}
if (!isset($args[3])) {
if ($cover_whole_arr) {
$array_type = Type::getEmptyArray();
AssignmentAnalyzer::assignByRefParam(
$statements_analyzer,
$array_arg,
$array_type,
$array_type,
$context,
false,
);
} elseif ($array_type) {
AssignmentAnalyzer::assignByRefParam(
$statements_analyzer,
$array_arg,
new Union([$array_type]),
new Union([$array_type]),
$context,
false,
);
} else {
$default_array_type = Type::getArray();
AssignmentAnalyzer::assignByRefParam(
$statements_analyzer,
$array_arg,
$default_array_type,
$default_array_type,
$context,
false,
);
}
return null;
}
@ -400,40 +524,31 @@ class ArrayFunctionArgumentsAnalyzer
$statements_analyzer->node_data->setType($replacement_arg, $replacement_arg_type);
}
if (($array_arg_type = $statements_analyzer->node_data->getType($array_arg))
&& $array_arg_type->hasArray()
if ($array_type
&& $replacement_arg_type
&& $replacement_arg_type->hasArray()
) {
/**
* @var TArray|TKeyedArray
*/
$array_type = $array_arg_type->getArray();
if ($array_type instanceof TKeyedArray) {
if ($array_type->is_list) {
$array_type = Type::getNonEmptyListAtomic($array_type->getGenericValueType());
} else {
$array_type = $array_type->getGenericArrayType();
}
}
if ($array_type instanceof TArray
&& $array_type->type_params[0]->hasInt()
&& !$array_type->type_params[0]->hasString()
) {
if ($array_type instanceof TNonEmptyArray) {
$array_type = Type::getNonEmptyListAtomic($array_type->type_params[1]);
} else {
$array_type = Type::getListAtomic($array_type->type_params[1]);
}
}
/**
* @var TArray|TKeyedArray
*/
$replacement_array_type = $replacement_arg_type->getArray();
if (($replacement_array_type_generic = ArrayType::infer($replacement_array_type))
&& $replacement_array_type_generic->count === 0
&& $cover_whole_arr) {
$empty_array_type = Type::getEmptyArray();
AssignmentAnalyzer::assignByRefParam(
$statements_analyzer,
$array_arg,
$empty_array_type,
$empty_array_type,
$context,
false,
);
return null;
}
if ($replacement_array_type instanceof TKeyedArray) {
$was_list = $replacement_array_type->is_list;
@ -462,16 +577,26 @@ class ArrayFunctionArgumentsAnalyzer
return null;
}
$array_type = Type::getArray();
AssignmentAnalyzer::assignByRefParam(
$statements_analyzer,
$array_arg,
$array_type,
$array_type,
$context,
false,
);
if ($array_type) {
AssignmentAnalyzer::assignByRefParam(
$statements_analyzer,
$array_arg,
new Union([$array_type]),
new Union([$array_type]),
$context,
false,
);
} else {
$default_array_type = Type::getArray();
AssignmentAnalyzer::assignByRefParam(
$statements_analyzer,
$array_arg,
$default_array_type,
$default_array_type,
$context,
false,
);
}
return null;
}

View File

@ -7,8 +7,11 @@ namespace Psalm\Internal\Type;
use Psalm\Type\Atomic;
use Psalm\Type\Atomic\TArray;
use Psalm\Type\Atomic\TKeyedArray;
use Psalm\Type\Atomic\TNonEmptyArray;
use Psalm\Type\Union;
use function count;
/**
* @internal
*/
@ -20,11 +23,14 @@ class ArrayType
public bool $is_list;
public function __construct(Union $key, Union $value, bool $is_list)
public ?int $count = null;
public function __construct(Union $key, Union $value, bool $is_list, ?int $count)
{
$this->key = $key;
$this->value = $value;
$this->is_list = $is_list;
$this->count = $count;
}
/**
@ -37,18 +43,35 @@ class ArrayType
public static function infer(Atomic $type): ?self
{
if ($type instanceof TKeyedArray) {
$count = null;
if ($type->isSealed()) {
$count = count($type->properties);
}
return new self(
$type->getGenericKeyType(),
$type->getGenericValueType(),
$type->is_list,
$count,
);
}
if ($type instanceof TArray) {
if ($type instanceof TNonEmptyArray) {
return new self(
$type->type_params[0],
$type->type_params[1],
false,
$type->count,
);
}
if ($type instanceof TArray) {
$empty = $type->isEmptyArray();
return new self(
$type->type_params[0],
$type->type_params[1],
false,
$empty?0:null,
);
}

View File

@ -144,6 +144,11 @@ class TKeyedArray extends Atomic
return $cloned;
}
public function isSealed(): bool
{
return $this->fallback_params === null;
}
public function getId(bool $exact = true, bool $nested = false): string
{
$property_strings = [];

View File

@ -1803,6 +1803,31 @@ class ArrayFunctionCallTest extends TestCase
'$d' => 'array<int, list{string}|string>',
],
],
'arraySpliceRefWithoutReplacement' => [
'code' => '<?php
$d = [1,2];
$o = 0;
array_splice($d, $o, 1);',
'assertions' => [
'$d' => 'list<int>',
],
],
'arraySpliceEmptyRefWithoutReplacement' => [
'code' => '<?php
$a = array( "hello" );
$_b = array_splice( $a, 0, 1 );',
'assertions' => [
'$a' => 'array<never, never>',
],
],
'arraySpliceEmptyRefWithEmptyReplacement' => [
'code' => '<?php
$a = array( "hello" );
$_b = array_splice( $a, 0, 1, [] );',
'assertions' => [
'$a' => 'array<never, never>',
],
],
'ksortPreserveShape' => [
'code' => '<?php
$a = ["a" => 3, "b" => 4];