mirror of
https://github.com/danog/psalm.git
synced 2024-11-26 20:34:47 +01:00
parent
c7eff18481
commit
16c33d1565
@ -143,7 +143,7 @@ class Context
|
||||
/**
|
||||
* A list of clauses in Conjunctive Normal Form
|
||||
*
|
||||
* @var array<int, Clause>
|
||||
* @var list<Clause>
|
||||
*/
|
||||
public $clauses = [];
|
||||
|
||||
@ -488,22 +488,24 @@ class Context
|
||||
*/
|
||||
public function removeReconciledClauses(array $changed_var_ids)
|
||||
{
|
||||
$this->clauses = array_filter(
|
||||
$this->clauses,
|
||||
/** @return bool */
|
||||
function (Clause $c) use ($changed_var_ids) {
|
||||
if ($c->wedge) {
|
||||
$this->clauses = \array_values(
|
||||
array_filter(
|
||||
$this->clauses,
|
||||
/** @return bool */
|
||||
function (Clause $c) use ($changed_var_ids) {
|
||||
if ($c->wedge) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ($c->possibilities as $key => $_) {
|
||||
if (in_array($key, $changed_var_ids, true)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ($c->possibilities as $key => $_) {
|
||||
if (in_array($key, $changed_var_ids, true)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -513,7 +515,7 @@ class Context
|
||||
* @param Union|null $new_type
|
||||
* @param StatementsAnalyzer|null $statements_analyzer
|
||||
*
|
||||
* @return array<int, Clause>
|
||||
* @return list<Clause>
|
||||
*/
|
||||
public static function filterClauses(
|
||||
$remove_var_id,
|
||||
|
@ -17,12 +17,12 @@ class ReturnTypeCollector
|
||||
* Gets the return types from a list of statements
|
||||
*
|
||||
* @param array<PhpParser\Node> $stmts
|
||||
* @param array<int,Type\Atomic> $yield_types
|
||||
* @param list<Type\Atomic> $yield_types
|
||||
* @param bool $ignore_nullable_issues
|
||||
* @param bool $ignore_falsable_issues
|
||||
* @param bool $collapse_types
|
||||
*
|
||||
* @return array<int,Type\Atomic> a list of return types
|
||||
* @return list<Type\Atomic> a list of return types
|
||||
*/
|
||||
public static function getReturnTypes(
|
||||
array $stmts,
|
||||
@ -272,6 +272,8 @@ class ReturnTypeCollector
|
||||
]
|
||||
),
|
||||
];
|
||||
} else {
|
||||
$yield_types = $yield_types;
|
||||
}
|
||||
}
|
||||
|
||||
@ -281,7 +283,7 @@ class ReturnTypeCollector
|
||||
/**
|
||||
* @param PhpParser\Node\Expr $stmt
|
||||
*
|
||||
* @return array<int, Atomic>
|
||||
* @return list<Atomic>
|
||||
*/
|
||||
protected static function getYieldTypeFromExpression(PhpParser\Node\Expr $stmt)
|
||||
{
|
||||
|
@ -437,12 +437,22 @@ class ForeachAnalyzer
|
||||
|
||||
if ($iterator_atomic_type instanceof Type\Atomic\TArray
|
||||
|| $iterator_atomic_type instanceof Type\Atomic\ObjectLike
|
||||
|| $iterator_atomic_type instanceof Type\Atomic\TList
|
||||
) {
|
||||
if ($iterator_atomic_type instanceof Type\Atomic\ObjectLike) {
|
||||
if (!$iterator_atomic_type->sealed) {
|
||||
$always_non_empty_array = false;
|
||||
}
|
||||
$iterator_atomic_type = $iterator_atomic_type->getGenericArrayType();
|
||||
} elseif ($iterator_atomic_type instanceof Type\Atomic\TList) {
|
||||
if (!$iterator_atomic_type instanceof Type\Atomic\TNonEmptyList) {
|
||||
$always_non_empty_array = false;
|
||||
}
|
||||
|
||||
$iterator_atomic_type = new Type\Atomic\TArray([
|
||||
Type::getInt(),
|
||||
$iterator_atomic_type->type_param
|
||||
]);
|
||||
} elseif (!$iterator_atomic_type instanceof Type\Atomic\TNonEmptyArray) {
|
||||
$always_non_empty_array = false;
|
||||
}
|
||||
|
@ -55,6 +55,8 @@ class ArrayAnalyzer
|
||||
|
||||
$codebase = $statements_analyzer->getCodebase();
|
||||
|
||||
$all_list = true;
|
||||
|
||||
foreach ($stmt->items as $int_offset => $item) {
|
||||
if ($item === null) {
|
||||
continue;
|
||||
@ -63,6 +65,8 @@ class ArrayAnalyzer
|
||||
$item_key_value = null;
|
||||
|
||||
if ($item->key) {
|
||||
$all_list = false;
|
||||
|
||||
if (ExpressionAnalyzer::analyze($statements_analyzer, $item->key, $context) === false) {
|
||||
return false;
|
||||
}
|
||||
@ -190,6 +194,7 @@ class ArrayAnalyzer
|
||||
} else {
|
||||
$item_value_type = null;
|
||||
}
|
||||
|
||||
// if this array looks like an object-like array, let's return that instead
|
||||
if ($item_value_type
|
||||
&& $item_key_type
|
||||
@ -198,12 +203,24 @@ class ArrayAnalyzer
|
||||
) {
|
||||
$object_like = new Type\Atomic\ObjectLike($property_types, $class_strings);
|
||||
$object_like->sealed = true;
|
||||
$object_like->is_list = $all_list;
|
||||
|
||||
$stmt->inferredType = new Type\Union([$object_like]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($all_list) {
|
||||
$array_type = new Type\Atomic\TNonEmptyList($item_value_type ?: Type::getMixed());
|
||||
$array_type->count = count($stmt->items);
|
||||
|
||||
$stmt->inferredType = new Type\Union([
|
||||
$array_type,
|
||||
]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$array_type = new Type\Atomic\TNonEmptyArray([
|
||||
$item_key_type ?: new Type\Union([new TInt, new TString]),
|
||||
$item_value_type ?: Type::getMixed(),
|
||||
|
@ -9,7 +9,9 @@ use Psalm\Context;
|
||||
use Psalm\Type;
|
||||
use Psalm\Type\Atomic\ObjectLike;
|
||||
use Psalm\Type\Atomic\TArray;
|
||||
use Psalm\Type\Atomic\TList;
|
||||
use Psalm\Type\Atomic\TNonEmptyArray;
|
||||
use Psalm\Type\Atomic\TNonEmptyList;
|
||||
use function array_reverse;
|
||||
use function array_shift;
|
||||
use function count;
|
||||
@ -340,12 +342,18 @@ class ArrayAssignmentAnalyzer
|
||||
$new_child_type = $child_stmt->inferredType; // noop
|
||||
}
|
||||
} else {
|
||||
$array_assignment_type = new Type\Union([
|
||||
new TArray([
|
||||
isset($current_dim->inferredType) ? $current_dim->inferredType : Type::getInt(),
|
||||
$current_type,
|
||||
]),
|
||||
]);
|
||||
if (!$current_dim) {
|
||||
$array_assignment_type = new Type\Union([
|
||||
new TList($current_type),
|
||||
]);
|
||||
} else {
|
||||
$array_assignment_type = new Type\Union([
|
||||
new TArray([
|
||||
$current_dim->inferredType ?? Type::getMixed(),
|
||||
$current_type,
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
$new_child_type = Type::combineUnionTypes(
|
||||
$child_stmt->inferredType,
|
||||
@ -436,29 +444,44 @@ class ArrayAssignmentAnalyzer
|
||||
} else {
|
||||
$array_atomic_key_type = Type::getMixed();
|
||||
}
|
||||
|
||||
$array_atomic_type = new TNonEmptyArray([
|
||||
$array_atomic_key_type,
|
||||
$current_type,
|
||||
]);
|
||||
} else {
|
||||
// todo: this can be improved I think
|
||||
$array_atomic_key_type = Type::getInt();
|
||||
$array_atomic_type = new TNonEmptyList($current_type);
|
||||
}
|
||||
|
||||
$array_atomic_type = new TNonEmptyArray([
|
||||
$array_atomic_key_type,
|
||||
$current_type,
|
||||
]);
|
||||
|
||||
$from_countable_object_like = false;
|
||||
|
||||
$new_child_type = null;
|
||||
|
||||
if (!$current_dim && !$context->inside_loop) {
|
||||
$atomic_root_types = $root_type->getTypes();
|
||||
|
||||
if (isset($atomic_root_types['array'])) {
|
||||
if ($atomic_root_types['array'] instanceof TNonEmptyArray) {
|
||||
if ($atomic_root_types['array'] instanceof TNonEmptyArray
|
||||
|| $atomic_root_types['array'] instanceof TNonEmptyList
|
||||
) {
|
||||
$array_atomic_type->count = $atomic_root_types['array']->count;
|
||||
} elseif ($atomic_root_types['array'] instanceof ObjectLike
|
||||
&& $atomic_root_types['array']->sealed
|
||||
) {
|
||||
$array_atomic_type->count = count($atomic_root_types['array']->properties);
|
||||
$from_countable_object_like = true;
|
||||
|
||||
if ($atomic_root_types['array']->is_list
|
||||
&& $array_atomic_type instanceof TList
|
||||
) {
|
||||
$array_atomic_type = clone $atomic_root_types['array'];
|
||||
|
||||
$new_child_type = new Type\Union([$array_atomic_type]);
|
||||
}
|
||||
} elseif ($array_atomic_type instanceof TList) {
|
||||
$array_atomic_type = new TNonEmptyList(
|
||||
$array_atomic_type->type_param
|
||||
);
|
||||
} else {
|
||||
$array_atomic_type = new TNonEmptyArray(
|
||||
$array_atomic_type->type_params
|
||||
@ -471,13 +494,15 @@ class ArrayAssignmentAnalyzer
|
||||
$array_atomic_type,
|
||||
]);
|
||||
|
||||
$new_child_type = Type::combineUnionTypes(
|
||||
$root_type,
|
||||
$array_assignment_type,
|
||||
$codebase,
|
||||
true,
|
||||
false
|
||||
);
|
||||
if (!$new_child_type) {
|
||||
$new_child_type = Type::combineUnionTypes(
|
||||
$root_type,
|
||||
$array_assignment_type,
|
||||
$codebase,
|
||||
true,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
if ($from_countable_object_like) {
|
||||
$atomic_root_types = $new_child_type->getTypes();
|
||||
|
@ -476,7 +476,7 @@ class AssignmentAnalyzer
|
||||
) {
|
||||
/**
|
||||
* @psalm-suppress PossiblyUndefinedArrayOffset
|
||||
* @var Type\Atomic\ObjectLike|Type\Atomic\TArray|null
|
||||
* @var Type\Atomic\ObjectLike|Type\Atomic\TList|Type\Atomic\TArray|null
|
||||
*/
|
||||
$array_value_type = isset($assign_value_type->getTypes()['array'])
|
||||
? $assign_value_type->getTypes()['array']
|
||||
@ -486,6 +486,13 @@ class AssignmentAnalyzer
|
||||
$array_value_type = $array_value_type->getGenericArrayType();
|
||||
}
|
||||
|
||||
if ($array_value_type instanceof Type\Atomic\TList) {
|
||||
$array_value_type = new Type\Atomic\TArray([
|
||||
Type::getInt(),
|
||||
$array_value_type->type_param
|
||||
]);
|
||||
}
|
||||
|
||||
self::analyze(
|
||||
$statements_analyzer,
|
||||
$var,
|
||||
@ -543,6 +550,8 @@ class AssignmentAnalyzer
|
||||
|
||||
if ($array_atomic_type instanceof Type\Atomic\TArray) {
|
||||
$new_assign_type = clone $array_atomic_type->type_params[1];
|
||||
} elseif ($array_atomic_type instanceof Type\Atomic\TList) {
|
||||
$new_assign_type = clone $array_atomic_type->type_param;
|
||||
} elseif ($array_atomic_type instanceof Type\Atomic\ObjectLike) {
|
||||
if ($assign_var_item->key
|
||||
&& ($assign_var_item->key instanceof PhpParser\Node\Scalar\String_
|
||||
|
@ -26,6 +26,7 @@ use Psalm\Type\Atomic\ObjectLike;
|
||||
use Psalm\Type\Atomic\TArray;
|
||||
use Psalm\Type\Atomic\TFalse;
|
||||
use Psalm\Type\Atomic\TFloat;
|
||||
use Psalm\Type\Atomic\TList;
|
||||
use Psalm\Type\Atomic\TTemplateParam;
|
||||
use Psalm\Type\Atomic\TInt;
|
||||
use Psalm\Type\Atomic\TMixed;
|
||||
@ -1072,19 +1073,34 @@ class BinaryOpAnalyzer
|
||||
|| $right_type_part instanceof TArray
|
||||
|| $left_type_part instanceof ObjectLike
|
||||
|| $right_type_part instanceof ObjectLike
|
||||
|| $left_type_part instanceof TList
|
||||
|| $right_type_part instanceof TList
|
||||
) {
|
||||
if ((!$right_type_part instanceof TArray && !$right_type_part instanceof ObjectLike)
|
||||
|| (!$left_type_part instanceof TArray && !$left_type_part instanceof ObjectLike)
|
||||
if ((!$right_type_part instanceof TArray
|
||||
&& !$right_type_part instanceof ObjectLike
|
||||
&& !$right_type_part instanceof TList)
|
||||
|| (!$left_type_part instanceof TArray
|
||||
&& !$left_type_part instanceof ObjectLike
|
||||
&& !$left_type_part instanceof TList)
|
||||
) {
|
||||
if (!$left_type_part instanceof TArray && !$left_type_part instanceof ObjectLike) {
|
||||
if (!$left_type_part instanceof TArray
|
||||
&& !$left_type_part instanceof ObjectLike
|
||||
&& !$left_type_part instanceof TList
|
||||
) {
|
||||
$invalid_left_messages[] = 'Cannot add an array to a non-array ' . $left_type_part;
|
||||
} else {
|
||||
$invalid_right_messages[] = 'Cannot add an array to a non-array ' . $right_type_part;
|
||||
}
|
||||
|
||||
if ($left_type_part instanceof TArray || $left_type_part instanceof ObjectLike) {
|
||||
if ($left_type_part instanceof TArray
|
||||
|| $left_type_part instanceof ObjectLike
|
||||
|| $left_type_part instanceof TList
|
||||
) {
|
||||
$has_valid_left_operand = true;
|
||||
} elseif ($right_type_part instanceof TArray || $right_type_part instanceof ObjectLike) {
|
||||
} elseif ($right_type_part instanceof TArray
|
||||
|| $right_type_part instanceof ObjectLike
|
||||
|| $right_type_part instanceof TList
|
||||
) {
|
||||
$has_valid_right_operand = true;
|
||||
}
|
||||
|
||||
|
@ -41,8 +41,10 @@ use Psalm\Type\Atomic\TArray;
|
||||
use Psalm\Type\Atomic\TClassString;
|
||||
use Psalm\Type\Atomic\TCallable;
|
||||
use Psalm\Type\Atomic\TEmpty;
|
||||
use Psalm\Type\Atomic\TList;
|
||||
use Psalm\Type\Atomic\TNamedObject;
|
||||
use Psalm\Type\Atomic\TNonEmptyArray;
|
||||
use Psalm\Type\Atomic\TNonEmptyList;
|
||||
use function strtolower;
|
||||
use function strpos;
|
||||
use function explode;
|
||||
@ -734,7 +736,7 @@ class CallAnalyzer
|
||||
) {
|
||||
/**
|
||||
* @psalm-suppress PossiblyUndefinedArrayOffset
|
||||
* @var TArray|ObjectLike
|
||||
* @var TArray|TList|ObjectLike
|
||||
*/
|
||||
$array_type = $arg->value->inferredType->getTypes()['array'];
|
||||
|
||||
@ -742,6 +744,10 @@ class CallAnalyzer
|
||||
$array_type = $array_type->getGenericArrayType();
|
||||
}
|
||||
|
||||
if ($array_type instanceof TList) {
|
||||
$array_type = new TArray([Type::getInt(), $array_type->type_param]);
|
||||
}
|
||||
|
||||
if (in_array($method_id, ['shuffle', 'sort', 'rsort', 'usort'], true)) {
|
||||
$tvalue = $array_type->type_params[1];
|
||||
$by_ref_type = new Type\Union([new TArray([Type::getInt(), clone $tvalue])]);
|
||||
@ -1059,6 +1065,23 @@ class CallAnalyzer
|
||||
$array_atomic_type = new TArray($array_atomic_type->type_params);
|
||||
}
|
||||
|
||||
$array_type->addType($array_atomic_type);
|
||||
} elseif ($array_atomic_type instanceof TNonEmptyList) {
|
||||
if (!$context->inside_loop && $array_atomic_type->count !== null) {
|
||||
if ($array_atomic_type->count === 0) {
|
||||
$array_atomic_type = new TArray(
|
||||
[
|
||||
new Type\Union([new TEmpty]),
|
||||
new Type\Union([new TEmpty]),
|
||||
]
|
||||
);
|
||||
} else {
|
||||
$array_atomic_type->count--;
|
||||
}
|
||||
} else {
|
||||
$array_atomic_type = new TList($array_atomic_type->type_param);
|
||||
}
|
||||
|
||||
$array_type->addType($array_atomic_type);
|
||||
}
|
||||
}
|
||||
@ -1677,13 +1700,17 @@ class CallAnalyzer
|
||||
if ($arg_type->hasArray()) {
|
||||
/**
|
||||
* @psalm-suppress PossiblyUndefinedArrayOffset
|
||||
* @var Type\Atomic\TArray|Type\Atomic\ObjectLike
|
||||
* @var Type\Atomic\TArray|Type\Atomic\TList|Type\Atomic\ObjectLike
|
||||
*/
|
||||
$array_atomic_type = $arg_type->getTypes()['array'];
|
||||
|
||||
if ($array_atomic_type instanceof Type\Atomic\ObjectLike) {
|
||||
$array_atomic_type = $array_atomic_type->getGenericArrayType();
|
||||
$arg_type_param = $array_atomic_type->getGenericValueType();
|
||||
} elseif ($array_atomic_type instanceof Type\Atomic\TList) {
|
||||
$arg_type_param = $array_atomic_type->type_param;
|
||||
} else {
|
||||
$arg_type_param = $array_atomic_type->type_params[1];
|
||||
}
|
||||
$arg_type_param = $array_atomic_type->type_params[1];
|
||||
} else {
|
||||
$arg_type_param = Type::getMixed();
|
||||
}
|
||||
@ -1756,15 +1783,17 @@ class CallAnalyzer
|
||||
if ($arg_type->hasArray()) {
|
||||
/**
|
||||
* @psalm-suppress PossiblyUndefinedArrayOffset
|
||||
* @var Type\Atomic\TArray|Type\Atomic\ObjectLike
|
||||
* @var Type\Atomic\TArray|Type\Atomic\TList|Type\Atomic\ObjectLike
|
||||
*/
|
||||
$array_atomic_type = $arg_type->getTypes()['array'];
|
||||
|
||||
if ($array_atomic_type instanceof Type\Atomic\ObjectLike) {
|
||||
$array_atomic_type = $array_atomic_type->getGenericArrayType();
|
||||
$arg_type = $array_atomic_type->getGenericValueType();
|
||||
} elseif ($array_atomic_type instanceof Type\Atomic\TList) {
|
||||
$arg_type = $array_atomic_type->type_param;
|
||||
} else {
|
||||
$arg_type = $array_atomic_type->type_params[1];
|
||||
}
|
||||
|
||||
$arg_type = $array_atomic_type->type_params[1];
|
||||
} else {
|
||||
foreach ($arg_type->getTypes() as $atomic_type) {
|
||||
if (!$atomic_type->isIterable($codebase)) {
|
||||
@ -1908,7 +1937,7 @@ class CallAnalyzer
|
||||
|
||||
/**
|
||||
* @psalm-suppress PossiblyUndefinedArrayOffset
|
||||
* @var ObjectLike|TArray|null
|
||||
* @var ObjectLike|TArray|TList|null
|
||||
*/
|
||||
$array_arg_type = $array_arg
|
||||
&& isset($array_arg->inferredType)
|
||||
@ -1921,6 +1950,10 @@ class CallAnalyzer
|
||||
$array_arg_type = $array_arg_type->getGenericArrayType();
|
||||
}
|
||||
|
||||
if ($array_arg_type instanceof TList) {
|
||||
$array_arg_type = new TArray([Type::getInt(), $array_arg_type->type_param]);
|
||||
}
|
||||
|
||||
$array_arg_types[] = $array_arg_type;
|
||||
}
|
||||
|
||||
|
@ -35,6 +35,7 @@ use Psalm\Type\Atomic\TLiteralInt;
|
||||
use Psalm\Type\Atomic\TLiteralString;
|
||||
use Psalm\Type\Atomic\TTemplateParam;
|
||||
use Psalm\Type\Atomic\TInt;
|
||||
use Psalm\Type\Atomic\TList;
|
||||
use Psalm\Type\Atomic\TMixed;
|
||||
use Psalm\Type\Atomic\TNamedObject;
|
||||
use Psalm\Type\Atomic\TNonEmptyArray;
|
||||
@ -455,37 +456,51 @@ class ArrayFetchAnalyzer
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($type instanceof TArray || $type instanceof ObjectLike) {
|
||||
if ($type instanceof TArray || $type instanceof ObjectLike || $type instanceof TList) {
|
||||
$has_array_access = true;
|
||||
|
||||
if ($in_assignment
|
||||
&& $type instanceof TArray
|
||||
&& (($type->type_params[0]->isEmpty() && $type->type_params[1]->isEmpty())
|
||||
|| ($type->type_params[1]->isMixed() && \is_string($key_value)))
|
||||
&& $key_value !== null
|
||||
) {
|
||||
$from_mixed_array = $type->type_params[1]->isMixed();
|
||||
$from_empty_array = $type->type_params[0]->isEmpty() && $type->type_params[1]->isEmpty();
|
||||
|
||||
$previous_key_type = $type->type_params[0];
|
||||
$previous_value_type = $type->type_params[1];
|
||||
if ($key_value !== null) {
|
||||
$from_mixed_array = $type->type_params[1]->isMixed();
|
||||
|
||||
// ok, type becomes an ObjectLike
|
||||
$array_type->removeType($type_string);
|
||||
$type = new ObjectLike([$key_value => $from_mixed_array ? Type::getMixed() : Type::getEmpty()]);
|
||||
$previous_key_type = $type->type_params[0];
|
||||
$previous_value_type = $type->type_params[1];
|
||||
|
||||
$type->sealed = $from_empty_array;
|
||||
// ok, type becomes an ObjectLike
|
||||
$array_type->removeType($type_string);
|
||||
$type = new ObjectLike([$key_value => $from_mixed_array ? Type::getMixed() : Type::getEmpty()]);
|
||||
|
||||
if (!$from_empty_array) {
|
||||
$type->previous_value_type = clone $previous_value_type;
|
||||
$type->previous_key_type = clone $previous_key_type;
|
||||
$type->sealed = $from_empty_array;
|
||||
|
||||
if (!$from_empty_array) {
|
||||
$type->previous_value_type = clone $previous_value_type;
|
||||
$type->previous_key_type = clone $previous_key_type;
|
||||
}
|
||||
|
||||
$array_type->addType($type);
|
||||
} elseif (!$stmt->dim && $from_empty_array && $replacement_type) {
|
||||
$array_type->removeType($type_string);
|
||||
$array_type->addType(new Type\Atomic\TNonEmptyList($replacement_type));
|
||||
continue;
|
||||
}
|
||||
|
||||
$array_type->addType($type);
|
||||
}
|
||||
|
||||
$offset_type = self::replaceOffsetTypeWithInts($offset_type);
|
||||
|
||||
if ($type instanceof TList
|
||||
&& (($in_assignment && $stmt->dim)
|
||||
|| $original_type instanceof TTemplateParam
|
||||
|| !$offset_type->isInt())
|
||||
) {
|
||||
$type = new TArray([Type::getInt(), $type->type_param]);
|
||||
}
|
||||
|
||||
if ($type instanceof TArray) {
|
||||
// if we're assigning to an empty array with a key offset, refashion that array
|
||||
if ($in_assignment) {
|
||||
@ -634,9 +649,52 @@ class ArrayFetchAnalyzer
|
||||
$array_access_type = Type::getMixed(true);
|
||||
}
|
||||
}
|
||||
} elseif ($type instanceof TList) {
|
||||
// if we're assigning to an empty array with a key offset, refashion that array
|
||||
if (!$in_assignment) {
|
||||
$expected_offset_type = Type::getInt();
|
||||
|
||||
if ($codebase->config->ensure_array_int_offsets_exist) {
|
||||
self::checkLiteralIntArrayOffset(
|
||||
$offset_type,
|
||||
$expected_offset_type,
|
||||
$array_var_id,
|
||||
$stmt,
|
||||
$context,
|
||||
$statements_analyzer
|
||||
);
|
||||
}
|
||||
|
||||
$has_valid_offset = true;
|
||||
}
|
||||
|
||||
if ($in_assignment && $type instanceof Type\Atomic\TNonEmptyList && $type->count !== null) {
|
||||
$type->count++;
|
||||
}
|
||||
|
||||
if ($in_assignment && $replacement_type) {
|
||||
$type->type_param = Type::combineUnionTypes(
|
||||
$type->type_param,
|
||||
$replacement_type,
|
||||
$codebase
|
||||
);
|
||||
}
|
||||
|
||||
if (!$array_access_type) {
|
||||
$array_access_type = $type->type_param;
|
||||
} else {
|
||||
$array_access_type = Type::combineUnionTypes(
|
||||
$array_access_type,
|
||||
$type->type_param
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$generic_key_type = $type->getGenericKeyType();
|
||||
|
||||
if (!$stmt->dim && $type->sealed && $type->is_list) {
|
||||
$key_value = count($type->properties);
|
||||
}
|
||||
|
||||
if ($key_value !== null) {
|
||||
if (isset($type->properties[$key_value]) || $replacement_type) {
|
||||
$has_valid_offset = true;
|
||||
|
@ -18,6 +18,7 @@ use Psalm\Type\Atomic\TEmptyMixed;
|
||||
use Psalm\Type\Atomic\TFalse;
|
||||
use Psalm\Type\Atomic\TFloat;
|
||||
use Psalm\Type\Atomic\TGenericObject;
|
||||
use Psalm\Type\Atomic\TList;
|
||||
use Psalm\Type\Atomic\TTemplateParam;
|
||||
use Psalm\Type\Atomic\GetClassT;
|
||||
use Psalm\Type\Atomic\GetTypeT;
|
||||
@ -31,6 +32,7 @@ use Psalm\Type\Atomic\TLiteralString;
|
||||
use Psalm\Type\Atomic\TMixed;
|
||||
use Psalm\Type\Atomic\TNamedObject;
|
||||
use Psalm\Type\Atomic\TNever;
|
||||
use Psalm\Type\Atomic\TNonEmptyList;
|
||||
use Psalm\Type\Atomic\TNull;
|
||||
use Psalm\Type\Atomic\TNumeric;
|
||||
use Psalm\Type\Atomic\TNumericString;
|
||||
@ -1029,9 +1031,14 @@ class TypeAnalyzer
|
||||
}
|
||||
|
||||
if ($container_type_part instanceof TIterable) {
|
||||
if ($input_type_part instanceof TArray || $input_type_part instanceof ObjectLike) {
|
||||
if ($input_type_part instanceof TArray
|
||||
|| $input_type_part instanceof ObjectLike
|
||||
|| $input_type_part instanceof TList
|
||||
) {
|
||||
if ($input_type_part instanceof ObjectLike) {
|
||||
$input_type_part = $input_type_part->getGenericArrayType();
|
||||
} elseif ($input_type_part instanceof TList) {
|
||||
$input_type_part = new TArray([Type::getInt(), $input_type_part->type_param]);
|
||||
}
|
||||
|
||||
$all_types_contain = true;
|
||||
@ -1867,8 +1874,45 @@ class TypeAnalyzer
|
||||
}
|
||||
}
|
||||
|
||||
if (($input_type_part instanceof TArray || $input_type_part instanceof ObjectLike)
|
||||
&& ($container_type_part instanceof TArray || $container_type_part instanceof ObjectLike)
|
||||
if ($container_type_part instanceof TList
|
||||
&& $input_type_part instanceof ObjectLike
|
||||
&& $input_type_part->is_list
|
||||
) {
|
||||
$input_type_part = $input_type_part->getList();
|
||||
}
|
||||
|
||||
if ($container_type_part instanceof TList
|
||||
&& $input_type_part instanceof TArray
|
||||
&& $input_type_part->type_params[1]->isEmpty()
|
||||
) {
|
||||
return !$container_type_part instanceof TNonEmptyList;
|
||||
}
|
||||
|
||||
if ($input_type_part instanceof TList
|
||||
&& $container_type_part instanceof TList
|
||||
) {
|
||||
if (!self::isContainedBy(
|
||||
$codebase,
|
||||
$input_type_part->type_param,
|
||||
$container_type_part->type_param,
|
||||
$input_type_part->type_param->ignore_nullable_issues,
|
||||
$input_type_part->type_param->ignore_falsable_issues,
|
||||
$atomic_comparison_result,
|
||||
$allow_interface_equality
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $input_type_part instanceof TNonEmptyList
|
||||
|| !$container_type_part instanceof TNonEmptyList;
|
||||
}
|
||||
|
||||
if (($input_type_part instanceof TArray
|
||||
|| $input_type_part instanceof ObjectLike
|
||||
|| $input_type_part instanceof TList)
|
||||
&& ($container_type_part instanceof TArray
|
||||
|| $container_type_part instanceof ObjectLike
|
||||
|| $container_type_part instanceof TList)
|
||||
) {
|
||||
if ($container_type_part instanceof ObjectLike) {
|
||||
$generic_container_type_part = $container_type_part->getGenericArrayType();
|
||||
@ -1887,6 +1931,7 @@ class TypeAnalyzer
|
||||
);
|
||||
|
||||
if (!$input_type_part instanceof ObjectLike
|
||||
&& !$input_type_part instanceof TList
|
||||
&& !$input_type_part->type_params[0]->hasMixed()
|
||||
&& !($input_type_part->type_params[1]->isEmpty()
|
||||
&& $container_params_can_be_undefined)
|
||||
@ -1902,6 +1947,17 @@ class TypeAnalyzer
|
||||
$input_type_part = $input_type_part->getGenericArrayType();
|
||||
}
|
||||
|
||||
if ($container_type_part instanceof TList) {
|
||||
$all_types_contain = false;
|
||||
$atomic_comparison_result->type_coerced = true;
|
||||
|
||||
$container_type_part = new TArray([Type::getInt(), clone $container_type_part->type_param]);
|
||||
}
|
||||
|
||||
if ($input_type_part instanceof TList) {
|
||||
$input_type_part = new TArray([Type::getInt(), clone $input_type_part->type_param]);
|
||||
}
|
||||
|
||||
$any_scalar_param_match = false;
|
||||
|
||||
foreach ($input_type_part->type_params as $i => $input_param) {
|
||||
|
@ -44,6 +44,11 @@ class ArrayColumnReturnTypeProvider implements \Psalm\Plugin\Hook\FunctionReturn
|
||||
if ($row_type->isSingle() && $row_type->hasArray()) {
|
||||
$row_shape = $row_type->getTypes()['array'];
|
||||
}
|
||||
} elseif ($input_array instanceof Type\Atomic\TList) {
|
||||
$row_type = $input_array->type_param;
|
||||
if ($row_type->isSingle() && $row_type->hasArray()) {
|
||||
$row_shape = $row_type->getTypes()['array'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -39,8 +39,9 @@ class ArrayFilterReturnTypeProvider implements \Psalm\Plugin\Hook\FunctionReturn
|
||||
&& isset($array_arg->inferredType)
|
||||
&& $array_arg->inferredType->hasType('array')
|
||||
&& ($array_atomic_type = $array_arg->inferredType->getTypes()['array'])
|
||||
&& ($array_atomic_type instanceof Type\Atomic\TArray ||
|
||||
$array_atomic_type instanceof Type\Atomic\ObjectLike)
|
||||
&& ($array_atomic_type instanceof Type\Atomic\TArray
|
||||
|| $array_atomic_type instanceof Type\Atomic\ObjectLike
|
||||
|| $array_atomic_type instanceof Type\Atomic\TList)
|
||||
? $array_atomic_type
|
||||
: null;
|
||||
|
||||
@ -51,6 +52,9 @@ class ArrayFilterReturnTypeProvider implements \Psalm\Plugin\Hook\FunctionReturn
|
||||
if ($first_arg_array instanceof Type\Atomic\TArray) {
|
||||
$inner_type = $first_arg_array->type_params[1];
|
||||
$key_type = clone $first_arg_array->type_params[0];
|
||||
} elseif ($first_arg_array instanceof Type\Atomic\TList) {
|
||||
$inner_type = $first_arg_array->type_param;
|
||||
$key_type = Type::getInt();
|
||||
} else {
|
||||
$inner_type = $first_arg_array->getGenericValueType();
|
||||
$key_type = $first_arg_array->getGenericKeyType();
|
||||
|
@ -43,7 +43,8 @@ class ArrayMapReturnTypeProvider implements \Psalm\Plugin\Hook\FunctionReturnTyp
|
||||
|
||||
if (isset($arg_types['array'])
|
||||
&& ($arg_types['array'] instanceof Type\Atomic\TArray
|
||||
|| $arg_types['array'] instanceof Type\Atomic\ObjectLike)
|
||||
|| $arg_types['array'] instanceof Type\Atomic\ObjectLike
|
||||
|| $arg_types['array'] instanceof Type\Atomic\TList)
|
||||
) {
|
||||
$array_arg_type = $arg_types['array'];
|
||||
}
|
||||
@ -55,6 +56,8 @@ class ArrayMapReturnTypeProvider implements \Psalm\Plugin\Hook\FunctionReturnTyp
|
||||
if (count($call_args) === 2) {
|
||||
if ($array_arg_type instanceof Type\Atomic\ObjectLike) {
|
||||
$generic_key_type = $array_arg_type->getGenericKeyType();
|
||||
} elseif ($array_arg_type instanceof Type\Atomic\TList) {
|
||||
$generic_key_type = Type::getInt();
|
||||
} else {
|
||||
$generic_key_type = $array_arg_type ? clone $array_arg_type->type_params[0] : Type::getArrayKey();
|
||||
}
|
||||
@ -90,6 +93,22 @@ class ArrayMapReturnTypeProvider implements \Psalm\Plugin\Hook\FunctionReturnTyp
|
||||
]);
|
||||
}
|
||||
|
||||
if ($array_arg_type instanceof Type\Atomic\TList) {
|
||||
if ($array_arg_type instanceof Type\Atomic\TNonEmptyList) {
|
||||
return new Type\Union([
|
||||
new Type\Atomic\TNonEmptyList(
|
||||
$inner_type,
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
return new Type\Union([
|
||||
new Type\Atomic\TList(
|
||||
$inner_type,
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
if ($array_arg_type instanceof Type\Atomic\TNonEmptyArray) {
|
||||
return new Type\Union([
|
||||
new Type\Atomic\TNonEmptyArray([
|
||||
|
@ -33,6 +33,8 @@ class ArrayMergeReturnTypeProvider implements \Psalm\Plugin\Hook\FunctionReturnT
|
||||
$codebase = $statements_source->getCodebase();
|
||||
|
||||
$generic_properties = [];
|
||||
$all_lists = true;
|
||||
$all_nonempty_lists = true;
|
||||
|
||||
foreach ($call_args as $call_arg) {
|
||||
if (!isset($call_arg->value->inferredType)) {
|
||||
@ -44,6 +46,8 @@ class ArrayMergeReturnTypeProvider implements \Psalm\Plugin\Hook\FunctionReturnT
|
||||
if (!$type_part instanceof Type\Atomic\TArray) {
|
||||
if ($type_part instanceof Type\Atomic\ObjectLike) {
|
||||
$type_part_value_type = $type_part->getGenericValueType();
|
||||
} elseif ($type_part instanceof Type\Atomic\TList) {
|
||||
$type_part_value_type = $type_part->type_param;
|
||||
} else {
|
||||
return Type::getArray();
|
||||
}
|
||||
@ -71,6 +75,12 @@ class ArrayMergeReturnTypeProvider implements \Psalm\Plugin\Hook\FunctionReturnT
|
||||
}
|
||||
|
||||
$unpacked_type_part = $unpacked_type_part->getGenericArrayType();
|
||||
} elseif ($unpacked_type_part instanceof Type\Atomic\TList) {
|
||||
$generic_properties = null;
|
||||
|
||||
if (!$unpacked_type_part instanceof Type\Atomic\TNonEmptyList) {
|
||||
$all_nonempty_lists = false;
|
||||
}
|
||||
} else {
|
||||
if ($unpacked_type_part instanceof Type\Atomic\TMixed
|
||||
&& $unpacked_type_part->from_loop_isset
|
||||
@ -87,17 +97,25 @@ class ArrayMergeReturnTypeProvider implements \Psalm\Plugin\Hook\FunctionReturnT
|
||||
$generic_properties = null;
|
||||
}
|
||||
|
||||
if ($unpacked_type_part->type_params[1]->isEmpty()) {
|
||||
continue;
|
||||
if ($unpacked_type_part instanceof Type\Atomic\TArray) {
|
||||
if ($unpacked_type_part->type_params[1]->isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$all_lists = false;
|
||||
}
|
||||
|
||||
$inner_key_types = array_merge(
|
||||
$inner_key_types,
|
||||
array_values($unpacked_type_part->type_params[0]->getTypes())
|
||||
$unpacked_type_part instanceof Type\Atomic\TList
|
||||
? [new Type\Atomic\TInt()]
|
||||
: array_values($unpacked_type_part->type_params[0]->getTypes())
|
||||
);
|
||||
$inner_value_types = array_merge(
|
||||
$inner_value_types,
|
||||
array_values($unpacked_type_part->type_params[1]->getTypes())
|
||||
$unpacked_type_part instanceof Type\Atomic\TList
|
||||
? array_values($unpacked_type_part->type_param->getTypes())
|
||||
: array_values($unpacked_type_part->type_params[1]->getTypes())
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -110,10 +128,26 @@ class ArrayMergeReturnTypeProvider implements \Psalm\Plugin\Hook\FunctionReturnT
|
||||
}
|
||||
|
||||
if ($inner_value_types) {
|
||||
$inner_value_type = TypeCombination::combineTypes($inner_value_types, $codebase, true);
|
||||
|
||||
if ($all_lists) {
|
||||
if ($all_nonempty_lists) {
|
||||
return new Type\Union([
|
||||
new Type\Atomic\TNonEmptyList($inner_value_type),
|
||||
]);
|
||||
}
|
||||
|
||||
return new Type\Union([
|
||||
new Type\Atomic\TList($inner_value_type),
|
||||
]);
|
||||
}
|
||||
|
||||
$inner_key_type = TypeCombination::combineTypes($inner_key_types, $codebase, true);
|
||||
|
||||
return new Type\Union([
|
||||
new Type\Atomic\TArray([
|
||||
TypeCombination::combineTypes($inner_key_types, $codebase, true),
|
||||
TypeCombination::combineTypes($inner_value_types, $codebase, true),
|
||||
$inner_key_type,
|
||||
$inner_value_type,
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
@ -30,8 +30,9 @@ class ArrayPointerAdjustmentReturnTypeProvider implements \Psalm\Plugin\Hook\Fun
|
||||
&& isset($first_arg->inferredType)
|
||||
&& $first_arg->inferredType->hasType('array')
|
||||
&& ($array_atomic_type = $first_arg->inferredType->getTypes()['array'])
|
||||
&& ($array_atomic_type instanceof Type\Atomic\TArray ||
|
||||
$array_atomic_type instanceof Type\Atomic\ObjectLike)
|
||||
&& ($array_atomic_type instanceof Type\Atomic\TArray
|
||||
|| $array_atomic_type instanceof Type\Atomic\ObjectLike
|
||||
|| $array_atomic_type instanceof Type\Atomic\TList)
|
||||
? $array_atomic_type
|
||||
: null;
|
||||
|
||||
@ -41,6 +42,8 @@ class ArrayPointerAdjustmentReturnTypeProvider implements \Psalm\Plugin\Hook\Fun
|
||||
|
||||
if ($first_arg_array instanceof Type\Atomic\TArray) {
|
||||
$value_type = clone $first_arg_array->type_params[1];
|
||||
} elseif ($first_arg_array instanceof Type\Atomic\TList) {
|
||||
$value_type = clone $first_arg_array->type_param;
|
||||
} else {
|
||||
$value_type = $first_arg_array->getGenericValueType();
|
||||
}
|
||||
|
@ -30,8 +30,9 @@ class ArrayPopReturnTypeProvider implements \Psalm\Plugin\Hook\FunctionReturnTyp
|
||||
&& isset($first_arg->inferredType)
|
||||
&& $first_arg->inferredType->hasType('array')
|
||||
&& ($array_atomic_type = $first_arg->inferredType->getTypes()['array'])
|
||||
&& ($array_atomic_type instanceof Type\Atomic\TArray ||
|
||||
$array_atomic_type instanceof Type\Atomic\ObjectLike)
|
||||
&& ($array_atomic_type instanceof Type\Atomic\TArray
|
||||
|| $array_atomic_type instanceof Type\Atomic\ObjectLike
|
||||
|| $array_atomic_type instanceof Type\Atomic\TList)
|
||||
? $array_atomic_type
|
||||
: null;
|
||||
|
||||
@ -51,6 +52,12 @@ class ArrayPopReturnTypeProvider implements \Psalm\Plugin\Hook\FunctionReturnTyp
|
||||
if (!$first_arg_array instanceof Type\Atomic\TNonEmptyArray) {
|
||||
$nullable = true;
|
||||
}
|
||||
} elseif ($first_arg_array instanceof Type\Atomic\TList) {
|
||||
$value_type = clone $first_arg_array->type_param;
|
||||
|
||||
if (!$first_arg_array instanceof Type\Atomic\TNonEmptyList) {
|
||||
$nullable = true;
|
||||
}
|
||||
} else {
|
||||
$value_type = $first_arg_array->getGenericValueType();
|
||||
|
||||
|
@ -31,8 +31,9 @@ class ArrayRandReturnTypeProvider implements \Psalm\Plugin\Hook\FunctionReturnTy
|
||||
&& isset($first_arg->inferredType)
|
||||
&& $first_arg->inferredType->hasType('array')
|
||||
&& ($array_atomic_type = $first_arg->inferredType->getTypes()['array'])
|
||||
&& ($array_atomic_type instanceof Type\Atomic\TArray ||
|
||||
$array_atomic_type instanceof Type\Atomic\ObjectLike)
|
||||
&& ($array_atomic_type instanceof Type\Atomic\TArray
|
||||
|| $array_atomic_type instanceof Type\Atomic\ObjectLike
|
||||
|| $array_atomic_type instanceof Type\Atomic\TList)
|
||||
? $array_atomic_type
|
||||
: null;
|
||||
|
||||
@ -42,6 +43,8 @@ class ArrayRandReturnTypeProvider implements \Psalm\Plugin\Hook\FunctionReturnTy
|
||||
|
||||
if ($first_arg_array instanceof Type\Atomic\TArray) {
|
||||
$key_type = clone $first_arg_array->type_params[0];
|
||||
} elseif ($first_arg_array instanceof Type\Atomic\TList) {
|
||||
$key_type = clone $first_arg_array->type_param;
|
||||
} else {
|
||||
$key_type = $first_arg_array->getGenericKeyType();
|
||||
}
|
||||
|
@ -53,12 +53,15 @@ class ArrayReduceReturnTypeProvider implements \Psalm\Plugin\Hook\FunctionReturn
|
||||
|
||||
if (isset($array_arg_types['array'])
|
||||
&& ($array_arg_types['array'] instanceof Type\Atomic\TArray
|
||||
|| $array_arg_types['array'] instanceof Type\Atomic\ObjectLike)
|
||||
|| $array_arg_types['array'] instanceof Type\Atomic\ObjectLike
|
||||
|| $array_arg_types['array'] instanceof Type\Atomic\TList)
|
||||
) {
|
||||
$array_arg_type = $array_arg_types['array'];
|
||||
|
||||
if ($array_arg_type instanceof Type\Atomic\ObjectLike) {
|
||||
$array_arg_type = $array_arg_type->getGenericArrayType();
|
||||
} elseif ($array_arg_type instanceof Type\Atomic\TList) {
|
||||
$array_arg_type = new Type\Atomic\TArray([Type::getInt(), clone $array_arg_type->type_param]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,14 +24,15 @@ class ArrayReverseReturnTypeProvider implements \Psalm\Plugin\Hook\FunctionRetur
|
||||
Context $context,
|
||||
CodeLocation $code_location
|
||||
) : Type\Union {
|
||||
$first_arg = isset($call_args[0]->value) ? $call_args[0]->value : null;
|
||||
$first_arg = $call_args[0]->value ?? null;
|
||||
|
||||
$first_arg_array = $first_arg
|
||||
&& isset($first_arg->inferredType)
|
||||
&& $first_arg->inferredType->hasType('array')
|
||||
&& ($array_atomic_type = $first_arg->inferredType->getTypes()['array'])
|
||||
&& ($array_atomic_type instanceof Type\Atomic\TArray ||
|
||||
$array_atomic_type instanceof Type\Atomic\ObjectLike)
|
||||
&& ($array_atomic_type instanceof Type\Atomic\TArray
|
||||
|| $array_atomic_type instanceof Type\Atomic\ObjectLike
|
||||
|| $array_atomic_type instanceof Type\Atomic\TList)
|
||||
? $array_atomic_type
|
||||
: null;
|
||||
|
||||
@ -43,6 +44,18 @@ class ArrayReverseReturnTypeProvider implements \Psalm\Plugin\Hook\FunctionRetur
|
||||
return new Type\Union([clone $first_arg_array]);
|
||||
}
|
||||
|
||||
if ($first_arg_array instanceof Type\Atomic\TList) {
|
||||
$second_arg = $call_args[1]->value ?? null;
|
||||
|
||||
if (!$second_arg
|
||||
|| (isset($second_arg->inferredType) && $second_arg->inferredType->isFalse())
|
||||
) {
|
||||
return new Type\Union([clone $first_arg_array]);
|
||||
}
|
||||
|
||||
return new Type\Union([new Type\Atomic\TArray([Type::getInt(), clone $first_arg_array->type_param])]);
|
||||
}
|
||||
|
||||
return new Type\Union([$first_arg_array->getGenericArrayType()]);
|
||||
}
|
||||
}
|
||||
|
@ -31,8 +31,9 @@ class ArraySliceReturnTypeProvider implements \Psalm\Plugin\Hook\FunctionReturnT
|
||||
&& isset($first_arg->inferredType)
|
||||
&& $first_arg->inferredType->hasType('array')
|
||||
&& ($array_atomic_type = $first_arg->inferredType->getTypes()['array'])
|
||||
&& ($array_atomic_type instanceof Type\Atomic\TArray ||
|
||||
$array_atomic_type instanceof Type\Atomic\ObjectLike)
|
||||
&& ($array_atomic_type instanceof Type\Atomic\TArray
|
||||
|| $array_atomic_type instanceof Type\Atomic\ObjectLike
|
||||
|| $array_atomic_type instanceof Type\Atomic\TList)
|
||||
? $array_atomic_type
|
||||
: null;
|
||||
|
||||
@ -44,6 +45,10 @@ class ArraySliceReturnTypeProvider implements \Psalm\Plugin\Hook\FunctionReturnT
|
||||
return new Type\Union([clone $first_arg_array]);
|
||||
}
|
||||
|
||||
if ($first_arg_array instanceof Type\Atomic\TList) {
|
||||
return new Type\Union([new Type\Atomic\TArray([Type::getInt(), clone $first_arg_array->type_param])]);
|
||||
}
|
||||
|
||||
return new Type\Union([$first_arg_array->getGenericArrayType()]);
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@
|
||||
* @param mixed $search_value
|
||||
* @param bool $strict
|
||||
*
|
||||
* @return array<int, T>
|
||||
* @return list<T>
|
||||
* @psalm-pure
|
||||
*/
|
||||
function array_keys(array $arr, $search_value = null, bool $strict = false)
|
||||
@ -18,7 +18,7 @@ function array_keys(array $arr, $search_value = null, bool $strict = false)
|
||||
*
|
||||
* @param array<mixed, T> $arr
|
||||
*
|
||||
* @return array<int, T>
|
||||
* @return list<T>
|
||||
* @psalm-pure
|
||||
*/
|
||||
function array_values(array $arr)
|
||||
|
@ -35,6 +35,7 @@ use Psalm\Type\Atomic\TClassString;
|
||||
use Psalm\Type\Atomic\TEmpty;
|
||||
use Psalm\Type\Atomic\TFalse;
|
||||
use Psalm\Type\Atomic\TInt;
|
||||
use Psalm\Type\Atomic\TList;
|
||||
use Psalm\Type\Atomic\TMixed;
|
||||
use Psalm\Type\Atomic\TNamedObject;
|
||||
use Psalm\Type\Atomic\TNull;
|
||||
@ -782,7 +783,7 @@ class AssertionReconciler extends \Psalm\Type\Reconciler
|
||||
$array_atomic_type = $existing_var_type->getTypes()['array'];
|
||||
$did_remove_type = false;
|
||||
|
||||
if ($array_atomic_type instanceof Type\Atomic\TArray
|
||||
if ($array_atomic_type instanceof TArray
|
||||
&& !$array_atomic_type instanceof Type\Atomic\TNonEmptyArray
|
||||
) {
|
||||
$did_remove_type = true;
|
||||
@ -795,6 +796,15 @@ class AssertionReconciler extends \Psalm\Type\Reconciler
|
||||
)
|
||||
);
|
||||
}
|
||||
} elseif ($array_atomic_type instanceof TList
|
||||
&& !$array_atomic_type instanceof Type\Atomic\TNonEmptyList
|
||||
) {
|
||||
$did_remove_type = true;
|
||||
$existing_var_type->addType(
|
||||
new Type\Atomic\TNonEmptyList(
|
||||
$array_atomic_type->type_param
|
||||
)
|
||||
);
|
||||
} elseif ($array_atomic_type instanceof Type\Atomic\ObjectLike) {
|
||||
foreach ($array_atomic_type->properties as $property_type) {
|
||||
if ($property_type->possibly_undefined) {
|
||||
@ -1528,6 +1538,8 @@ class AssertionReconciler extends \Psalm\Type\Reconciler
|
||||
if ($type->isArrayAccessibleWithStringKey($codebase)) {
|
||||
if (get_class($type) === TArray::class) {
|
||||
$array_types[] = new Atomic\TNonEmptyArray($type->type_params);
|
||||
} elseif (get_class($type) === TList::class) {
|
||||
$array_types[] = new Atomic\TNonEmptyList($type->type_param);
|
||||
} else {
|
||||
$array_types[] = $type;
|
||||
}
|
||||
@ -1844,6 +1856,7 @@ class AssertionReconciler extends \Psalm\Type\Reconciler
|
||||
$array_atomic_type = $existing_var_atomic_types['array'];
|
||||
|
||||
if ($array_atomic_type instanceof Type\Atomic\TNonEmptyArray
|
||||
|| $array_atomic_type instanceof Type\Atomic\TNonEmptyList
|
||||
|| ($array_atomic_type instanceof Type\Atomic\ObjectLike
|
||||
&& array_filter(
|
||||
$array_atomic_type->properties,
|
||||
|
@ -443,6 +443,7 @@ class NegatedAssertionReconciler extends Reconciler
|
||||
$did_remove_type = false;
|
||||
|
||||
if ($array_atomic_type instanceof Type\Atomic\TNonEmptyArray
|
||||
|| $array_atomic_type instanceof Type\Atomic\TNonEmptyList
|
||||
|| ($array_atomic_type instanceof Type\Atomic\ObjectLike && $array_atomic_type->sealed)
|
||||
) {
|
||||
$did_remove_type = true;
|
||||
@ -923,6 +924,16 @@ class NegatedAssertionReconciler extends Reconciler
|
||||
)
|
||||
);
|
||||
}
|
||||
} elseif ($array_atomic_type instanceof Type\Atomic\TList
|
||||
&& !$array_atomic_type instanceof Type\Atomic\TNonEmptyList
|
||||
) {
|
||||
$did_remove_type = true;
|
||||
|
||||
$existing_var_type->addType(
|
||||
new Type\Atomic\TNonEmptyList(
|
||||
$array_atomic_type->type_param
|
||||
)
|
||||
);
|
||||
} elseif ($array_atomic_type instanceof Type\Atomic\ObjectLike
|
||||
&& !$array_atomic_type->sealed
|
||||
) {
|
||||
|
@ -28,6 +28,7 @@ use Psalm\Type\Atomic\TFloat;
|
||||
use Psalm\Type\Atomic\TGenericObject;
|
||||
use Psalm\Type\Atomic\TInt;
|
||||
use Psalm\Type\Atomic\TIterable;
|
||||
use Psalm\Type\Atomic\TList;
|
||||
use Psalm\Type\Atomic\TLiteralClassString;
|
||||
use Psalm\Type\Atomic\TLiteralFloat;
|
||||
use Psalm\Type\Atomic\TLiteralInt;
|
||||
@ -35,6 +36,7 @@ use Psalm\Type\Atomic\TLiteralString;
|
||||
use Psalm\Type\Atomic\TMixed;
|
||||
use Psalm\Type\Atomic\TNamedObject;
|
||||
use Psalm\Type\Atomic\TNonEmptyArray;
|
||||
use Psalm\Type\Atomic\TNonEmptyList;
|
||||
use Psalm\Type\Atomic\TNonEmptyMixed;
|
||||
use Psalm\Type\Atomic\TNull;
|
||||
use Psalm\Type\Atomic\TObject;
|
||||
@ -114,6 +116,9 @@ class TypeCombination
|
||||
*/
|
||||
private $extra_types;
|
||||
|
||||
/** @var ?bool */
|
||||
private $all_arrays_lists;
|
||||
|
||||
/**
|
||||
* Combines types together
|
||||
* - so `int + string = int|string`
|
||||
@ -123,7 +128,7 @@ class TypeCombination
|
||||
* - and `array<string> + array<empty> = array<string>`
|
||||
* - and `array + array<string> = array<mixed>`
|
||||
*
|
||||
* @param array<Atomic> $types
|
||||
* @param list<Atomic> $types
|
||||
* @param int $literal_limit any greater number of literal types than this
|
||||
* will be merged to a scalar
|
||||
*
|
||||
@ -321,6 +326,10 @@ class TypeCombination
|
||||
$objectlike->previous_value_type = $combination->objectlike_value_type;
|
||||
}
|
||||
|
||||
if ($combination->all_arrays_lists) {
|
||||
$objectlike->is_list = true;
|
||||
}
|
||||
|
||||
$new_types[] = $objectlike;
|
||||
} else {
|
||||
$new_types[] = new Type\Atomic\TArray([Type::getArrayKey(), Type::getMixed()]);
|
||||
@ -410,14 +419,25 @@ class TypeCombination
|
||||
&& $combination->objectlike_sealed
|
||||
&& $overwrite_empty_array)
|
||||
) {
|
||||
if ($combination->array_counts && count($combination->array_counts) === 1) {
|
||||
$array_type = new TNonEmptyArray($generic_type_params);
|
||||
$array_type->count = array_keys($combination->array_counts)[0];
|
||||
if ($combination->all_arrays_lists) {
|
||||
$array_type = new TNonEmptyList($generic_type_params[1]);
|
||||
|
||||
if ($combination->array_counts && count($combination->array_counts) === 1) {
|
||||
$array_type->count = array_keys($combination->array_counts)[0];
|
||||
}
|
||||
} else {
|
||||
$array_type = new TNonEmptyArray($generic_type_params);
|
||||
|
||||
if ($combination->array_counts && count($combination->array_counts) === 1) {
|
||||
$array_type->count = array_keys($combination->array_counts)[0];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$array_type = new TArray($generic_type_params);
|
||||
if ($combination->all_arrays_lists) {
|
||||
$array_type = new TList($generic_type_params[1]);
|
||||
} else {
|
||||
$array_type = new TArray($generic_type_params);
|
||||
}
|
||||
}
|
||||
|
||||
$new_types[] = $array_type;
|
||||
@ -671,6 +691,41 @@ class TypeCombination
|
||||
} else {
|
||||
$combination->array_always_filled = false;
|
||||
}
|
||||
|
||||
if (!$type->type_params[1]->isEmpty()) {
|
||||
$combination->all_arrays_lists = false;
|
||||
}
|
||||
} elseif ($type instanceof TList) {
|
||||
foreach ([Type::getInt(), $type->type_param] as $i => $type_param) {
|
||||
if (isset($combination->array_type_params[$i])) {
|
||||
$combination->array_type_params[$i] = Type::combineUnionTypes(
|
||||
$combination->array_type_params[$i],
|
||||
$type_param,
|
||||
$codebase,
|
||||
$overwrite_empty_array
|
||||
);
|
||||
} else {
|
||||
$combination->array_type_params[$i] = $type_param;
|
||||
}
|
||||
}
|
||||
|
||||
if ($type instanceof TNonEmptyList) {
|
||||
if ($combination->array_counts !== null) {
|
||||
if ($type->count === null) {
|
||||
$combination->array_counts = null;
|
||||
} else {
|
||||
$combination->array_counts[$type->count] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$combination->array_sometimes_filled = true;
|
||||
} else {
|
||||
$combination->array_always_filled = false;
|
||||
}
|
||||
|
||||
if ($combination->all_arrays_lists !== false) {
|
||||
$combination->all_arrays_lists = true;
|
||||
}
|
||||
} elseif (($type instanceof TGenericObject && ($type->value === 'Traversable' || $type->value === 'Generator'))
|
||||
|| ($type instanceof TIterable && $type->has_docblock_params)
|
||||
|| ($type instanceof TArray && $type_key === 'iterable')
|
||||
@ -759,8 +814,14 @@ class TypeCombination
|
||||
$combination->array_counts[count($type->properties)] = true;
|
||||
}
|
||||
|
||||
foreach ($possibly_undefined_entries as $type) {
|
||||
$type->possibly_undefined = true;
|
||||
foreach ($possibly_undefined_entries as $possibly_undefined_type) {
|
||||
$possibly_undefined_type->possibly_undefined = true;
|
||||
}
|
||||
|
||||
if (!$type->is_list) {
|
||||
$combination->all_arrays_lists = false;
|
||||
} elseif ($combination->all_arrays_lists !== false) {
|
||||
$combination->all_arrays_lists = true;
|
||||
}
|
||||
} else {
|
||||
if ($type instanceof TObject) {
|
||||
|
@ -36,12 +36,14 @@ use Psalm\Type\Atomic\TFloat;
|
||||
use Psalm\Type\Atomic\TGenericObject;
|
||||
use Psalm\Type\Atomic\TInt;
|
||||
use Psalm\Type\Atomic\TIterable;
|
||||
use Psalm\Type\Atomic\TList;
|
||||
use Psalm\Type\Atomic\TLiteralClassString;
|
||||
use Psalm\Type\Atomic\TLiteralFloat;
|
||||
use Psalm\Type\Atomic\TLiteralInt;
|
||||
use Psalm\Type\Atomic\TLiteralString;
|
||||
use Psalm\Type\Atomic\TMixed;
|
||||
use Psalm\Type\Atomic\TNamedObject;
|
||||
use Psalm\Type\Atomic\TNonEmptyList;
|
||||
use Psalm\Type\Atomic\TNull;
|
||||
use Psalm\Type\Atomic\TNumeric;
|
||||
use Psalm\Type\Atomic\TObject;
|
||||
@ -103,6 +105,7 @@ abstract class Type
|
||||
'key-of' => true,
|
||||
'value-of' => true,
|
||||
'non-empty-countable' => true,
|
||||
'list' => true,
|
||||
];
|
||||
|
||||
/**
|
||||
@ -276,6 +279,14 @@ abstract class Type
|
||||
return new TIterable($generic_params);
|
||||
}
|
||||
|
||||
if ($generic_type_value === 'list') {
|
||||
return new TList($generic_params[0]);
|
||||
}
|
||||
|
||||
if ($generic_type_value === 'non-empty-list') {
|
||||
return new TNonEmptyList($generic_params[0]);
|
||||
}
|
||||
|
||||
if ($generic_type_value === 'class-string') {
|
||||
$class_name = (string) $generic_params[0];
|
||||
|
||||
|
@ -297,7 +297,7 @@ class Algebra
|
||||
*
|
||||
* @param array<int, Clause> $clauses
|
||||
*
|
||||
* @return array<int, Clause>
|
||||
* @return list<Clause>
|
||||
*/
|
||||
public static function simplifyCNF(array $clauses)
|
||||
{
|
||||
@ -404,7 +404,7 @@ class Algebra
|
||||
/**
|
||||
* Look for clauses with only one possible value
|
||||
*
|
||||
* @param array<int, Clause> $clauses
|
||||
* @param list<Clause> $clauses
|
||||
* @param array<string, bool> $cond_referenced_var_ids
|
||||
*
|
||||
* @return array<string, array<int, array<int, string>>>
|
||||
@ -628,7 +628,7 @@ class Algebra
|
||||
*
|
||||
* @param array<int, Clause> $clauses
|
||||
*
|
||||
* @return array<int, Clause>
|
||||
* @return list<Clause>
|
||||
*/
|
||||
public static function negateFormula(array $clauses, int $complexity = null)
|
||||
{
|
||||
|
@ -464,6 +464,12 @@ class Reconciler
|
||||
if ($existing_key_type_part instanceof Type\Atomic\TArray) {
|
||||
$new_base_type_candidate = clone $existing_key_type_part->type_params[1];
|
||||
|
||||
if ($has_isset) {
|
||||
$new_base_type_candidate->possibly_undefined = true;
|
||||
}
|
||||
} elseif ($existing_key_type_part instanceof Type\Atomic\TList) {
|
||||
$new_base_type_candidate = clone $existing_key_type_part->type_param;
|
||||
|
||||
if ($has_isset) {
|
||||
$new_base_type_candidate->possibly_undefined = true;
|
||||
}
|
||||
|
@ -659,6 +659,13 @@ class ArrayAccessTest extends TestCase
|
||||
/** @psalm-suppress MixedPropertyFetch */
|
||||
print_r([&$a->foo->bar]);',
|
||||
],
|
||||
'accessOffsetOnList' => [
|
||||
'<?php
|
||||
/** @param list<int> $arr */
|
||||
function foo(array $arr) : void {
|
||||
echo $arr[3] ?? null;
|
||||
}',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -42,7 +42,7 @@ class ArrayAssignmentTest extends TestCase
|
||||
|
||||
$out[] = 4;',
|
||||
'assertions' => [
|
||||
'$out' => 'non-empty-array<int, int>',
|
||||
'$out' => 'non-empty-list<int>',
|
||||
],
|
||||
],
|
||||
'genericArrayCreationWithInt' => [
|
||||
@ -53,7 +53,7 @@ class ArrayAssignmentTest extends TestCase
|
||||
$out[] = 4;
|
||||
}',
|
||||
'assertions' => [
|
||||
'$out' => 'non-empty-array<int, int>',
|
||||
'$out' => 'non-empty-list<int>',
|
||||
],
|
||||
],
|
||||
'generic2dArrayCreation' => [
|
||||
@ -64,7 +64,7 @@ class ArrayAssignmentTest extends TestCase
|
||||
$out[] = [4];
|
||||
}',
|
||||
'assertions' => [
|
||||
'$out' => 'non-empty-array<int, array{0: int}>',
|
||||
'$out' => 'non-empty-list<array{0: int}>',
|
||||
],
|
||||
],
|
||||
'generic2dArrayCreationAddedInIf' => [
|
||||
@ -84,7 +84,7 @@ class ArrayAssignmentTest extends TestCase
|
||||
|
||||
$out[] = $bits;',
|
||||
'assertions' => [
|
||||
'$out' => 'non-empty-array<int, non-empty-array<int, int>>',
|
||||
'$out' => 'non-empty-list<non-empty-list<int>>',
|
||||
],
|
||||
],
|
||||
'genericArrayCreationWithObjectAddedInIf' => [
|
||||
@ -97,7 +97,7 @@ class ArrayAssignmentTest extends TestCase
|
||||
$out[] = new B();
|
||||
}',
|
||||
'assertions' => [
|
||||
'$out' => 'array<int, B>',
|
||||
'$out' => 'list<B>',
|
||||
],
|
||||
],
|
||||
'genericArrayCreationWithElementAddedInSwitch' => [
|
||||
@ -113,7 +113,7 @@ class ArrayAssignmentTest extends TestCase
|
||||
// do nothing
|
||||
}',
|
||||
'assertions' => [
|
||||
'$out' => 'array<int, int>',
|
||||
'$out' => 'list<int>',
|
||||
],
|
||||
],
|
||||
'genericArrayCreationWithElementsAddedInSwitch' => [
|
||||
@ -130,7 +130,7 @@ class ArrayAssignmentTest extends TestCase
|
||||
break;
|
||||
}',
|
||||
'assertions' => [
|
||||
'$out' => 'array<int, string|int>',
|
||||
'$out' => 'list<string|int>',
|
||||
],
|
||||
],
|
||||
'genericArrayCreationWithElementsAddedInSwitchWithNothing' => [
|
||||
@ -150,15 +150,7 @@ class ArrayAssignmentTest extends TestCase
|
||||
// do nothing
|
||||
}',
|
||||
'assertions' => [
|
||||
'$out' => 'array<int, string|int>',
|
||||
],
|
||||
],
|
||||
'implicitIntArrayCreation' => [
|
||||
'<?php
|
||||
$foo = [];
|
||||
$foo[] = "hello";',
|
||||
'assertions' => [
|
||||
'$foo' => 'non-empty-array<int, string>',
|
||||
'$out' => 'list<string|int>',
|
||||
],
|
||||
],
|
||||
'implicit2dIntArrayCreation' => [
|
||||
@ -166,7 +158,7 @@ class ArrayAssignmentTest extends TestCase
|
||||
$foo = [];
|
||||
$foo[][] = "hello";',
|
||||
'assertions' => [
|
||||
'$foo' => 'non-empty-array<int, array<int, string>>',
|
||||
'$foo' => 'non-empty-list<array<int, string>>',
|
||||
],
|
||||
],
|
||||
'implicit3dIntArrayCreation' => [
|
||||
@ -174,7 +166,7 @@ class ArrayAssignmentTest extends TestCase
|
||||
$foo = [];
|
||||
$foo[][][] = "hello";',
|
||||
'assertions' => [
|
||||
'$foo' => 'non-empty-array<int, array<int, array<int, string>>>',
|
||||
'$foo' => 'non-empty-list<list<array<int, string>>>',
|
||||
],
|
||||
],
|
||||
'implicit4dIntArrayCreation' => [
|
||||
@ -182,7 +174,7 @@ class ArrayAssignmentTest extends TestCase
|
||||
$foo = [];
|
||||
$foo[][][][] = "hello";',
|
||||
'assertions' => [
|
||||
'$foo' => 'non-empty-array<int, array<int, array<int, array<int, string>>>>',
|
||||
'$foo' => 'non-empty-list<list<list<array<int, string>>>>',
|
||||
],
|
||||
],
|
||||
'implicitIndexedIntArrayCreation' => [
|
||||
@ -296,9 +288,9 @@ class ArrayAssignmentTest extends TestCase
|
||||
$foo["b"][] = "goodbye";
|
||||
$bar = $foo["a"];',
|
||||
'assertions' => [
|
||||
'$foo' => 'array{a: string, b: array<int, string>}',
|
||||
'$foo' => 'array{a: string, b: non-empty-list<string>}',
|
||||
'$foo[\'a\']' => 'string',
|
||||
'$foo[\'b\']' => 'array<int, string>',
|
||||
'$foo[\'b\']' => 'non-empty-list<string>',
|
||||
'$bar' => 'string',
|
||||
],
|
||||
],
|
||||
@ -368,8 +360,8 @@ class ArrayAssignmentTest extends TestCase
|
||||
$c = [];
|
||||
$c[$b][$b][] = "bam";',
|
||||
'assertions' => [
|
||||
'$a' => 'array{boop: array<int, string>}',
|
||||
'$c' => 'array{boop: non-empty-array<string, array<int, string>>}',
|
||||
'$a' => 'array{boop: non-empty-list<string>}',
|
||||
'$c' => 'array{boop: non-empty-array<string, non-empty-list<string>>}',
|
||||
],
|
||||
],
|
||||
'assignExplicitValueToGeneric' => [
|
||||
@ -783,7 +775,7 @@ class ArrayAssignmentTest extends TestCase
|
||||
$a = null;
|
||||
$a[0][] = 1;',
|
||||
'assertions' => [
|
||||
'$a' => 'array{0: array<int, int>}',
|
||||
'$a' => 'array{0: non-empty-list<int>}',
|
||||
],
|
||||
'error_levels' => ['PossiblyNullArrayAssignment'],
|
||||
],
|
||||
@ -820,8 +812,8 @@ class ArrayAssignmentTest extends TestCase
|
||||
$a_keys = array_keys($a);',
|
||||
'assertions' => [
|
||||
'$a' => 'array{0: string, 1: int}',
|
||||
'$a_values' => 'array<int, string|int>',
|
||||
'$a_keys' => 'array<int, int>',
|
||||
'$a_values' => 'list<string|int>',
|
||||
'$a_keys' => 'list<int>',
|
||||
],
|
||||
],
|
||||
'changeIntOffsetKeyValuesWithDirectAssignment' => [
|
||||
@ -871,7 +863,7 @@ class ArrayAssignmentTest extends TestCase
|
||||
$a = null;
|
||||
}',
|
||||
'assertions' => [
|
||||
'$a' => 'non-empty-array<int, int>|null',
|
||||
'$a' => 'non-empty-list<int>|null',
|
||||
],
|
||||
],
|
||||
'assignArrayOrSetNullInElseIf' => [
|
||||
@ -887,7 +879,7 @@ class ArrayAssignmentTest extends TestCase
|
||||
$a = null;
|
||||
}',
|
||||
'assertions' => [
|
||||
'$a' => 'array<int, int>|null',
|
||||
'$a' => 'list<int>|null',
|
||||
],
|
||||
],
|
||||
'assignArrayOrSetNullInElse' => [
|
||||
@ -903,7 +895,7 @@ class ArrayAssignmentTest extends TestCase
|
||||
$a = null;
|
||||
}',
|
||||
'assertions' => [
|
||||
'$a' => 'non-empty-array<int, int>|null',
|
||||
'$a' => 'non-empty-list<int>|null',
|
||||
],
|
||||
],
|
||||
'mixedMethodCallArrayAccess' => [
|
||||
@ -1145,6 +1137,93 @@ class ArrayAssignmentTest extends TestCase
|
||||
return $array;
|
||||
}'
|
||||
],
|
||||
'listUsedAsArray' => [
|
||||
'<?php
|
||||
function takesArray(array $arr) : void {}
|
||||
|
||||
$a = [];
|
||||
$a[] = 1;
|
||||
$a[] = 2;
|
||||
|
||||
takesArray($a);',
|
||||
'assertions' => [
|
||||
'$a' => 'non-empty-list<int>'
|
||||
],
|
||||
],
|
||||
'listTakesEmptyArray' => [
|
||||
'<?php
|
||||
/** @param list<int> $arr */
|
||||
function takesList(array $arr) : void {}
|
||||
|
||||
$a = [];
|
||||
|
||||
takesList($a);',
|
||||
'assertions' => [
|
||||
'$a' => 'array<empty, empty>'
|
||||
],
|
||||
],
|
||||
'listCreatedInSingleStatementUsedAsArray' => [
|
||||
'<?php
|
||||
function takesArray(array $arr) : void {}
|
||||
|
||||
/** @param list<int> $arr */
|
||||
function takesList(array $arr) : void {}
|
||||
|
||||
$a = [1, 2];
|
||||
|
||||
takesArray($a);
|
||||
takesList($a);
|
||||
|
||||
$a[] = 3;
|
||||
|
||||
takesArray($a);
|
||||
takesList($a);
|
||||
|
||||
$b = $a;
|
||||
|
||||
$b[] = rand(0, 10);',
|
||||
'assertions' => [
|
||||
'$a' => 'array{0: int, 1: int, 2: int}',
|
||||
'$b' => 'array{0: int, 1: int, 2: int, 3: int}',
|
||||
],
|
||||
],
|
||||
'listMergedWithObjectLikeList' => [
|
||||
'<?php
|
||||
/** @param list<int> $arr */
|
||||
function takesAnotherList(array $arr) : void {}
|
||||
|
||||
/** @param list<int> $arr */
|
||||
function takesList(array $arr) : void {
|
||||
if (rand(0, 1)) {
|
||||
$arr = [1, 2, 3];
|
||||
}
|
||||
|
||||
takesAnotherList($arr);
|
||||
}',
|
||||
],
|
||||
'listMergedWithObjectLikeListAfterAssertion' => [
|
||||
'<?php
|
||||
/** @param list<int> $arr */
|
||||
function takesAnotherList(array $arr) : void {}
|
||||
|
||||
/** @param list<int> $arr */
|
||||
function takesList(array $arr) : void {
|
||||
if ($arr) {
|
||||
$arr = [4, 5, 6];
|
||||
}
|
||||
|
||||
takesAnotherList($arr);
|
||||
}',
|
||||
],
|
||||
'nonEmptyAssertionOnListElement' => [
|
||||
'<?php
|
||||
/** @param list<array<string, string>> $arr */
|
||||
function takesList(array $arr) : void {
|
||||
if (!empty($arr[0])) {
|
||||
foreach ($arr[0] as $k => $v) {}
|
||||
}
|
||||
}',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@ -1313,6 +1392,30 @@ class ArrayAssignmentTest extends TestCase
|
||||
$storage[$key] = "test";',
|
||||
'error_message' => 'InvalidArgument',
|
||||
],
|
||||
'listUsedAsArrayWrongType' => [
|
||||
'<?php
|
||||
/** @param string[] $arr */
|
||||
function takesArray(array $arr) : void {}
|
||||
|
||||
$a = [];
|
||||
$a[] = 1;
|
||||
$a[] = 2;
|
||||
|
||||
takesArray($a);',
|
||||
'error_message' => 'InvalidScalarArgument',
|
||||
],
|
||||
'listUsedAsArrayWrongListType' => [
|
||||
'<?php
|
||||
/** @param list<string> $arr */
|
||||
function takesArray(array $arr) : void {}
|
||||
|
||||
$a = [];
|
||||
$a[] = 1;
|
||||
$a[] = 2;
|
||||
|
||||
takesArray($a);',
|
||||
'error_message' => 'InvalidScalarArgument',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -197,7 +197,7 @@ class FunctionCallTest extends TestCase
|
||||
'<?php
|
||||
$a = array_keys(["a" => 1, "b" => 2]);',
|
||||
'assertions' => [
|
||||
'$a' => 'array<int, string>',
|
||||
'$a' => 'list<string>',
|
||||
],
|
||||
],
|
||||
'arrayKeysMixed' => [
|
||||
@ -206,15 +206,17 @@ class FunctionCallTest extends TestCase
|
||||
$b = ["a" => 5];
|
||||
$a = array_keys($b);',
|
||||
'assertions' => [
|
||||
'$a' => 'array<int, array-key>',
|
||||
'$a' => 'list<array-key>',
|
||||
],
|
||||
'error_levels' => ['MixedArgument'],
|
||||
],
|
||||
'arrayValues' => [
|
||||
'<?php
|
||||
$b = array_values(["a" => 1, "b" => 2]);',
|
||||
$b = array_values(["a" => 1, "b" => 2]);
|
||||
$c = array_values(["a" => "hello", "b" => "jello"]);',
|
||||
'assertions' => [
|
||||
'$b' => 'array<int, int>',
|
||||
'$b' => 'list<int>',
|
||||
'$c' => 'list<string>',
|
||||
],
|
||||
],
|
||||
'arrayCombine' => [
|
||||
|
@ -94,7 +94,7 @@ class Php56Test extends TestCase
|
||||
|
||||
array_push($a, ...$b);',
|
||||
'assertions' => [
|
||||
'$a' => 'non-empty-array<int, string>',
|
||||
'$a' => 'non-empty-list<string>',
|
||||
],
|
||||
],
|
||||
'arrayMergeArgumentUnpacking' => [
|
||||
|
@ -13,21 +13,23 @@ class TypeCombinationTest extends TestCase
|
||||
* @dataProvider providerTestValidTypeCombination
|
||||
*
|
||||
* @param string $expected
|
||||
* @param array<int, string> $types
|
||||
* @param list<string> $types
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testValidTypeCombination($expected, $types)
|
||||
{
|
||||
foreach ($types as $k => $type) {
|
||||
$types[$k] = self::getAtomic($type);
|
||||
$types[$k]->setFromDocblock();
|
||||
$converted_types = [];
|
||||
|
||||
foreach ($types as $type) {
|
||||
$converted_type = self::getAtomic($type);
|
||||
$converted_type->setFromDocblock();
|
||||
$converted_types[] = $converted_type;
|
||||
}
|
||||
|
||||
/** @psalm-suppress InvalidArgument */
|
||||
$this->assertSame(
|
||||
$expected,
|
||||
(string) TypeCombination::combineTypes($types)
|
||||
(string) TypeCombination::combineTypes($converted_types)
|
||||
);
|
||||
}
|
||||
|
||||
@ -58,7 +60,7 @@ class TypeCombinationTest extends TestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string,array{string,array<int,string>}>
|
||||
* @return array<string,array{string,list<string>}>
|
||||
*/
|
||||
public function providerTestValidTypeCombination()
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user