mirror of
https://github.com/danog/psalm.git
synced 2024-11-30 04:39:00 +01:00
Fix #75 - allow inteeger offsets for object-like arrays
This commit is contained in:
parent
0da9c10d36
commit
ea63fd7ae3
@ -142,6 +142,7 @@ class AssignmentChecker
|
||||
} elseif ($assign_var instanceof PhpParser\Node\Expr\List_
|
||||
|| $assign_var instanceof PhpParser\Node\Expr\Array_
|
||||
) {
|
||||
/** @var int $offset */
|
||||
foreach ($assign_var->items as $offset => $assign_var_item) {
|
||||
// $assign_var_item can be null e.g. list($a, ) = ['a', 'b']
|
||||
if (!$assign_var_item) {
|
||||
@ -162,6 +163,21 @@ class AssignmentChecker
|
||||
$doc_comment
|
||||
);
|
||||
|
||||
continue;
|
||||
} elseif (isset($assign_value_type->types['array']) &&
|
||||
$assign_value_type->types['array'] instanceof Type\Atomic\ObjectLike &&
|
||||
!$assign_var_item->key &&
|
||||
isset($assign_value_type->types['array']->properties[$offset]) // if object-like has int offsets
|
||||
) {
|
||||
self::analyze(
|
||||
$statements_checker,
|
||||
$var,
|
||||
null,
|
||||
$assign_value_type->types['array']->properties[$offset],
|
||||
$context,
|
||||
$doc_comment
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -658,7 +658,8 @@ class FetchChecker
|
||||
) {
|
||||
$var_type = null;
|
||||
$key_type = null;
|
||||
$key_value = null;
|
||||
$string_key_value = null;
|
||||
$int_key_value = null;
|
||||
|
||||
$nesting = 0;
|
||||
$var_id = ExpressionChecker::getVarId(
|
||||
@ -695,7 +696,9 @@ class FetchChecker
|
||||
$key_type = $stmt->dim->inferredType;
|
||||
|
||||
if ($stmt->dim instanceof PhpParser\Node\Scalar\String_) {
|
||||
$key_value = $stmt->dim->value;
|
||||
$string_key_value = $stmt->dim->value;
|
||||
} elseif ($stmt->dim instanceof PhpParser\Node\Scalar\LNumber) {
|
||||
$int_key_value = $stmt->dim->value;
|
||||
}
|
||||
} else {
|
||||
$key_type = Type::getMixed();
|
||||
@ -782,7 +785,7 @@ class FetchChecker
|
||||
$array_assignment,
|
||||
$key_type,
|
||||
$keyed_assignment_type,
|
||||
$key_value
|
||||
$string_key_value
|
||||
) === false) {
|
||||
return false;
|
||||
}
|
||||
@ -816,7 +819,10 @@ class FetchChecker
|
||||
}
|
||||
|
||||
if ($inferred_key_type) {
|
||||
Type::combineUnionTypes($key_type, $type->type_params[0]);
|
||||
$inferred_key_type = Type::combineUnionTypes(
|
||||
$inferred_key_type,
|
||||
$type->type_params[0]
|
||||
);
|
||||
} else {
|
||||
$inferred_key_type = $type->type_params[0];
|
||||
}
|
||||
@ -855,7 +861,7 @@ class FetchChecker
|
||||
$type->type_params[1]->isEmpty()
|
||||
)
|
||||
) {
|
||||
$properties = $key_value ? [$key_value => $keyed_assignment_type] : [];
|
||||
$properties = $string_key_value ? [$string_key_value => $keyed_assignment_type] : [];
|
||||
|
||||
$assignment_type = new Type\Union([
|
||||
new Type\Atomic\ObjectLike($properties)
|
||||
@ -931,22 +937,24 @@ class FetchChecker
|
||||
} elseif ($type instanceof Type\Atomic\TArray && $value_index !== null) {
|
||||
$stmt->inferredType = $type->type_params[$value_index];
|
||||
} elseif ($type instanceof Type\Atomic\ObjectLike) {
|
||||
if ($key_value && isset($type->properties[$key_value])) {
|
||||
$stmt->inferredType = clone $type->properties[$key_value];
|
||||
} elseif ($key_type->hasInt()) {
|
||||
$object_like_keys = array_keys($type->properties);
|
||||
if ($object_like_keys) {
|
||||
if (count($object_like_keys) === 1) {
|
||||
$expected_keys_string = '\'' . $object_like_keys[0] . '\'';
|
||||
} else {
|
||||
$last_key = array_pop($object_like_keys);
|
||||
$expected_keys_string = '\'' . implode('\', \'', $object_like_keys) .
|
||||
'\' or \'' . $last_key . '\'';
|
||||
}
|
||||
$object_like_keys = array_keys($type->properties);
|
||||
if ($object_like_keys) {
|
||||
if (count($object_like_keys) === 1) {
|
||||
$expected_keys_string = '\'' . $object_like_keys[0] . '\'';
|
||||
} else {
|
||||
$expected_keys_string = 'string';
|
||||
$last_key = array_pop($object_like_keys);
|
||||
$expected_keys_string = '\'' . implode('\', \'', $object_like_keys) .
|
||||
'\' or \'' . $last_key . '\'';
|
||||
}
|
||||
} else {
|
||||
$expected_keys_string = 'string';
|
||||
}
|
||||
|
||||
if ($string_key_value && isset($type->properties[$string_key_value])) {
|
||||
$stmt->inferredType = clone $type->properties[$string_key_value];
|
||||
} elseif ($int_key_value !== null && isset($type->properties[$int_key_value])) {
|
||||
$stmt->inferredType = clone $type->properties[$int_key_value];
|
||||
} elseif ($key_type->hasInt()) {
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidArrayAccess(
|
||||
'Cannot access value on array variable ' . $var_id . ' using int offset - ' .
|
||||
|
@ -854,4 +854,26 @@ class ArrayAssignmentTest extends PHPUnit_Framework_TestCase
|
||||
$file_checker = new FileChecker('somefile.php', $this->project_checker, $stmts);
|
||||
$file_checker->visitAndAnalyzeMethods($context);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testObjectLikeWithIntegerKeys()
|
||||
{
|
||||
$stmts = self::$parser->parse('<?php
|
||||
/** @var array{0: string, 1: int} **/
|
||||
$a = ["hello", 5];
|
||||
$b = $a[0]; // string
|
||||
$c = $a[1]; // int
|
||||
list($d, $e) = $a; // $d is string, $e is int
|
||||
');
|
||||
|
||||
$file_checker = new FileChecker('somefile.php', $this->project_checker, $stmts);
|
||||
$context = new Context();
|
||||
$file_checker->visitAndAnalyzeMethods($context);
|
||||
$this->assertEquals('string', (string) $context->vars_in_scope['$b']);
|
||||
$this->assertEquals('int', (string) $context->vars_in_scope['$c']);
|
||||
$this->assertEquals('string', (string) $context->vars_in_scope['$d']);
|
||||
$this->assertEquals('int', (string) $context->vars_in_scope['$e']);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user