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\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;
} }

View File

@ -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,
); );
} }

View File

@ -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 = [];

View File

@ -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];