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\Analyzer\StatementsAnalyzer;
|
||||||
use Psalm\Internal\Codebase\InternalCallMapHandler;
|
use Psalm\Internal\Codebase\InternalCallMapHandler;
|
||||||
use Psalm\Internal\MethodIdentifier;
|
use Psalm\Internal\MethodIdentifier;
|
||||||
|
use Psalm\Internal\Type\ArrayType;
|
||||||
use Psalm\Internal\Type\Comparator\TypeComparisonResult;
|
use Psalm\Internal\Type\Comparator\TypeComparisonResult;
|
||||||
use Psalm\Internal\Type\Comparator\UnionTypeComparator;
|
use Psalm\Internal\Type\Comparator\UnionTypeComparator;
|
||||||
use Psalm\Internal\Type\TemplateResult;
|
use Psalm\Internal\Type\TemplateResult;
|
||||||
@ -47,6 +48,7 @@ use function array_unshift;
|
|||||||
use function assert;
|
use function assert;
|
||||||
use function count;
|
use function count;
|
||||||
use function explode;
|
use function explode;
|
||||||
|
use function is_numeric;
|
||||||
use function strpos;
|
use function strpos;
|
||||||
use function strtolower;
|
use function strtolower;
|
||||||
use function substr;
|
use function substr;
|
||||||
@ -346,6 +348,40 @@ class ArrayFunctionArgumentsAnalyzer
|
|||||||
return false;
|
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;
|
$offset_arg = $args[1]->value;
|
||||||
|
|
||||||
if (ExpressionAnalyzer::analyze(
|
if (ExpressionAnalyzer::analyze(
|
||||||
@ -356,7 +392,47 @@ class ArrayFunctionArgumentsAnalyzer
|
|||||||
return false;
|
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 (!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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -370,7 +446,55 @@ class ArrayFunctionArgumentsAnalyzer
|
|||||||
return false;
|
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 (!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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -400,40 +524,31 @@ class ArrayFunctionArgumentsAnalyzer
|
|||||||
$statements_analyzer->node_data->setType($replacement_arg, $replacement_arg_type);
|
$statements_analyzer->node_data->setType($replacement_arg, $replacement_arg_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (($array_arg_type = $statements_analyzer->node_data->getType($array_arg))
|
if ($array_type
|
||||||
&& $array_arg_type->hasArray()
|
|
||||||
&& $replacement_arg_type
|
&& $replacement_arg_type
|
||||||
&& $replacement_arg_type->hasArray()
|
&& $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
|
* @var TArray|TKeyedArray
|
||||||
*/
|
*/
|
||||||
$replacement_array_type = $replacement_arg_type->getArray();
|
$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) {
|
if ($replacement_array_type instanceof TKeyedArray) {
|
||||||
$was_list = $replacement_array_type->is_list;
|
$was_list = $replacement_array_type->is_list;
|
||||||
|
|
||||||
@ -462,16 +577,26 @@ class ArrayFunctionArgumentsAnalyzer
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$array_type = Type::getArray();
|
if ($array_type) {
|
||||||
|
AssignmentAnalyzer::assignByRefParam(
|
||||||
AssignmentAnalyzer::assignByRefParam(
|
$statements_analyzer,
|
||||||
$statements_analyzer,
|
$array_arg,
|
||||||
$array_arg,
|
new Union([$array_type]),
|
||||||
$array_type,
|
new Union([$array_type]),
|
||||||
$array_type,
|
$context,
|
||||||
$context,
|
false,
|
||||||
false,
|
);
|
||||||
);
|
} else {
|
||||||
|
$default_array_type = Type::getArray();
|
||||||
|
AssignmentAnalyzer::assignByRefParam(
|
||||||
|
$statements_analyzer,
|
||||||
|
$array_arg,
|
||||||
|
$default_array_type,
|
||||||
|
$default_array_type,
|
||||||
|
$context,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,11 @@ namespace Psalm\Internal\Type;
|
|||||||
use Psalm\Type\Atomic;
|
use Psalm\Type\Atomic;
|
||||||
use Psalm\Type\Atomic\TArray;
|
use Psalm\Type\Atomic\TArray;
|
||||||
use Psalm\Type\Atomic\TKeyedArray;
|
use Psalm\Type\Atomic\TKeyedArray;
|
||||||
|
use Psalm\Type\Atomic\TNonEmptyArray;
|
||||||
use Psalm\Type\Union;
|
use Psalm\Type\Union;
|
||||||
|
|
||||||
|
use function count;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
@ -20,11 +23,14 @@ class ArrayType
|
|||||||
|
|
||||||
public bool $is_list;
|
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->key = $key;
|
||||||
$this->value = $value;
|
$this->value = $value;
|
||||||
$this->is_list = $is_list;
|
$this->is_list = $is_list;
|
||||||
|
$this->count = $count;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -37,18 +43,35 @@ class ArrayType
|
|||||||
public static function infer(Atomic $type): ?self
|
public static function infer(Atomic $type): ?self
|
||||||
{
|
{
|
||||||
if ($type instanceof TKeyedArray) {
|
if ($type instanceof TKeyedArray) {
|
||||||
|
$count = null;
|
||||||
|
if ($type->isSealed()) {
|
||||||
|
$count = count($type->properties);
|
||||||
|
}
|
||||||
|
|
||||||
return new self(
|
return new self(
|
||||||
$type->getGenericKeyType(),
|
$type->getGenericKeyType(),
|
||||||
$type->getGenericValueType(),
|
$type->getGenericValueType(),
|
||||||
$type->is_list,
|
$type->is_list,
|
||||||
|
$count,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($type instanceof TArray) {
|
if ($type instanceof TNonEmptyArray) {
|
||||||
return new self(
|
return new self(
|
||||||
$type->type_params[0],
|
$type->type_params[0],
|
||||||
$type->type_params[1],
|
$type->type_params[1],
|
||||||
false,
|
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;
|
return $cloned;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isSealed(): bool
|
||||||
|
{
|
||||||
|
return $this->fallback_params === null;
|
||||||
|
}
|
||||||
|
|
||||||
public function getId(bool $exact = true, bool $nested = false): string
|
public function getId(bool $exact = true, bool $nested = false): string
|
||||||
{
|
{
|
||||||
$property_strings = [];
|
$property_strings = [];
|
||||||
|
@ -1803,6 +1803,31 @@ class ArrayFunctionCallTest extends TestCase
|
|||||||
'$d' => 'array<int, list{string}|string>',
|
'$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' => [
|
'ksortPreserveShape' => [
|
||||||
'code' => '<?php
|
'code' => '<?php
|
||||||
$a = ["a" => 3, "b" => 4];
|
$a = ["a" => 3, "b" => 4];
|
||||||
|
Loading…
Reference in New Issue
Block a user