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:
commit
cee88f2c4b
@ -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;
|
||||
}
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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 = [];
|
||||
|
@ -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];
|
||||
|
Loading…
Reference in New Issue
Block a user