1
0
mirror of https://github.com/danog/psalm.git synced 2024-12-15 19:07:00 +01:00
psalm/src/Psalm/Checker/Statements/Expression/ArrayChecker.php

168 lines
5.5 KiB
PHP
Raw Normal View History

2018-01-14 18:09:40 +01:00
<?php
namespace Psalm\Checker\Statements\Expression;
use PhpParser;
use Psalm\Checker\Statements\ExpressionChecker;
use Psalm\Checker\StatementsChecker;
use Psalm\CodeLocation;
2018-01-14 18:09:40 +01:00
use Psalm\Context;
use Psalm\Issue\DuplicateArrayKey;
use Psalm\IssueBuffer;
2018-01-14 18:09:40 +01:00
use Psalm\Type;
use Psalm\Type\Atomic\TInt;
use Psalm\Type\Atomic\TString;
class ArrayChecker
{
/**
* @param StatementsChecker $statements_checker
* @param PhpParser\Node\Expr\Array_ $stmt
* @param Context $context
*
* @return false|null
*/
public static function analyze(
StatementsChecker $statements_checker,
PhpParser\Node\Expr\Array_ $stmt,
Context $context
) {
// if the array is empty, this special type allows us to match any other array type against it
if (empty($stmt->items)) {
$stmt->inferredType = Type::getEmptyArray();
return null;
}
$item_key_type = null;
$item_value_type = null;
$property_types = [];
$can_create_objectlike = true;
$array_keys = [];
/** @var int $int_offset */
2018-01-14 18:09:40 +01:00
foreach ($stmt->items as $int_offset => $item) {
if ($item === null) {
continue;
}
$item_key_value = null;
2018-01-14 18:09:40 +01:00
if ($item->key) {
if (ExpressionChecker::analyze($statements_checker, $item->key, $context) === false) {
return false;
}
if ($item->key instanceof PhpParser\Node\Scalar\String_
|| $item->key instanceof PhpParser\Node\Scalar\LNumber
) {
$item_key_value = $item->key->value;
}
2018-01-14 18:09:40 +01:00
if (isset($item->key->inferredType)) {
$key_type = $item->key->inferredType;
if ($item->key instanceof PhpParser\Node\Scalar\String_
&& preg_match('/^(0|[1-9][0-9]*)$/', $item->key->value)
) {
$key_type = Type::getInt(false, (int) $item->key->value);
}
2018-01-14 18:09:40 +01:00
if ($item_key_type) {
$item_key_type = Type::combineUnionTypes($key_type, $item_key_type);
2018-01-14 18:09:40 +01:00
} else {
$item_key_type = $key_type;
2018-01-14 18:09:40 +01:00
}
if ($item_key_value === null) {
if ($item->key->inferredType->isSingleStringLiteral()) {
$item_key_value = $item->key->inferredType->getSingleStringLiteral();
} elseif ($item->key->inferredType->isSingleIntLiteral()) {
$item_key_value = $item->key->inferredType->getSingleIntLiteral();
}
}
2018-01-14 18:09:40 +01:00
}
} else {
$item_key_value = $int_offset;
2018-01-14 18:09:40 +01:00
$item_key_type = Type::getInt();
}
if ($item_key_value !== null) {
if (isset($array_keys[$item_key_value])) {
if (IssueBuffer::accepts(
new DuplicateArrayKey(
'Key \'' . $item_key_value . '\' already exists on array',
new CodeLocation($statements_checker->getSource(), $item)
),
$statements_checker->getSuppressedIssues()
)) {
// fall through
}
}
$array_keys[$item_key_value] = true;
}
if (ExpressionChecker::analyze($statements_checker, $item->value, $context) === false) {
return false;
}
2018-01-14 18:09:40 +01:00
if ($item_value_type && $item_value_type->isMixed() && !$can_create_objectlike) {
continue;
}
if (isset($item->value->inferredType)) {
if ($item_key_value !== null) {
$property_types[$item_key_value] = $item->value->inferredType;
2018-01-14 18:09:40 +01:00
} else {
$can_create_objectlike = false;
}
if ($item_value_type) {
$item_value_type = Type::combineUnionTypes($item->value->inferredType, clone $item_value_type);
2018-01-14 18:09:40 +01:00
} else {
$item_value_type = $item->value->inferredType;
}
} else {
$item_value_type = Type::getMixed();
if ($item_key_value !== null) {
$property_types[$item_key_value] = $item_value_type;
2018-01-14 18:09:40 +01:00
} else {
$can_create_objectlike = false;
}
}
}
// if this array looks like an object-like array, let's return that instead
if ($item_value_type
&& $item_key_type
&& ($item_key_type->hasString() || $item_key_type->hasInt())
&& $can_create_objectlike
) {
$object_like = new Type\Atomic\ObjectLike($property_types);
$object_like->sealed = true;
$stmt->inferredType = new Type\Union([$object_like]);
2018-01-14 18:09:40 +01:00
return null;
}
$array_type = new Type\Atomic\TArray([
$item_key_type ?: new Type\Union([new TInt, new TString]),
$item_value_type ?: Type::getMixed(),
]);
$array_type->count = count($stmt->items);
2018-01-14 18:09:40 +01:00
$stmt->inferredType = new Type\Union([
$array_type,
2018-01-14 18:09:40 +01:00
]);
return null;
}
}