1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-21 21:31:13 +01:00

Fix #1260 - improve handling of ArrayAccess assignment

This commit is contained in:
Brown 2019-01-31 12:45:47 -05:00
parent c195e8fd21
commit 2054e3753f
5 changed files with 89 additions and 16 deletions

View File

@ -17,9 +17,10 @@ use Psalm\Type\Atomic\TNonEmptyArray;
class ArrayAssignmentAnalyzer
{
/**
* @param StatementsAnalyzer $statements_analyzer
* @param StatementsAnalyzer $statements_analyzer
* @param PhpParser\Node\Expr\ArrayDimFetch $stmt
* @param Context $context
* @param PhpParser\Node\Expr|null $assign_value
* @param Type\Union $assignment_value_type
*
* @return void
@ -29,6 +30,7 @@ class ArrayAssignmentAnalyzer
StatementsAnalyzer $statements_analyzer,
PhpParser\Node\Expr\ArrayDimFetch $stmt,
Context $context,
$assign_value,
Type\Union $assignment_value_type
) {
$nesting = 0;
@ -42,6 +44,7 @@ class ArrayAssignmentAnalyzer
self::updateArrayType(
$statements_analyzer,
$stmt,
$assign_value,
$assignment_value_type,
$context
);
@ -55,6 +58,7 @@ class ArrayAssignmentAnalyzer
* @param StatementsAnalyzer $statements_analyzer
* @param PhpParser\Node\Expr\ArrayDimFetch $stmt
* @param Type\Union $assignment_type
* @param PhpParser\Node\Expr|null $assign_value
* @param Context $context
*
* @return false|null
@ -62,6 +66,7 @@ class ArrayAssignmentAnalyzer
public static function updateArrayType(
StatementsAnalyzer $statements_analyzer,
PhpParser\Node\Expr\ArrayDimFetch $stmt,
$assign_value,
Type\Union $assignment_type,
Context $context
) {
@ -201,6 +206,7 @@ class ArrayAssignmentAnalyzer
true,
$array_var_id,
$context,
$child_stmts ? null : $assign_value,
$child_stmts ? null : $assignment_type
);

View File

@ -501,6 +501,7 @@ class AssignmentAnalyzer
$statements_analyzer,
$assign_var,
$context,
$assign_value,
$assign_value_type
);
} elseif ($assign_var instanceof PhpParser\Node\Expr\PropertyFetch) {
@ -663,6 +664,7 @@ class AssignmentAnalyzer
$statements_analyzer,
$stmt->var,
$context,
$stmt->expr,
$result_type ?: Type::getMixed($context->inside_loop)
);
} elseif ($result_type && $array_var_id) {

View File

@ -1001,6 +1001,7 @@ class BinaryOpAnalyzer
ArrayAssignmentAnalyzer::updateArrayType(
$statements_source,
$left,
$right,
$result_type,
$context
);

View File

@ -252,6 +252,7 @@ class ArrayFetchAnalyzer
$in_assignment,
$array_var_id,
Context $context,
PhpParser\Node\Expr $assign_value = null,
Type\Union $replacement_type = null
) {
$codebase = $statements_analyzer->getCodebase();
@ -717,10 +718,10 @@ class ArrayFetchAnalyzer
continue;
}
if ($type instanceof TNamedObject && $stmt->dim) {
if ($type instanceof TNamedObject) {
if (strtolower($type->value) === 'simplexmlelement') {
$array_access_type = Type::getMixed();
} elseif (strtolower($type->value) === 'domnodelist') {
} elseif (strtolower($type->value) === 'domnodelist' && $stmt->dim) {
$fake_method_call = new PhpParser\Node\Expr\MethodCall(
$stmt->var,
new PhpParser\Node\Identifier('item', $stmt->var->getAttributes()),
@ -747,20 +748,46 @@ class ArrayFetchAnalyzer
$iterator_class_type = $fake_method_call->inferredType ?? null;
$array_access_type = $iterator_class_type ?: Type::getMixed();
} elseif (strtolower($type->value) === 'arrayaccess'
|| (($codebase->classExists($type->value)
&& $codebase->classImplements($type->value, 'ArrayAccess'))
|| ($codebase->interfaceExists($type->value)
&& $codebase->interfaceExtends($type->value, 'ArrayAccess'))
)
} elseif ((strtolower($type->value) === 'arrayaccess'
|| (($codebase->classExists($type->value)
&& $codebase->classImplements($type->value, 'ArrayAccess'))
|| ($codebase->interfaceExists($type->value)
&& $codebase->interfaceExtends($type->value, 'ArrayAccess'))
))
&& ($stmt->dim || $in_assignment)
) {
$fake_method_call = new PhpParser\Node\Expr\MethodCall(
$stmt->var,
new PhpParser\Node\Identifier('offsetGet', $stmt->var->getAttributes()),
[
new PhpParser\Node\Arg($stmt->dim)
]
);
if ($in_assignment) {
$fake_method_call = new PhpParser\Node\Expr\MethodCall(
$stmt->var,
new PhpParser\Node\Identifier('offsetSet', $stmt->var->getAttributes()),
[
new PhpParser\Node\Arg(
$stmt->dim
? $stmt->dim
: new PhpParser\Node\Expr\ConstFetch(
new PhpParser\Node\Name('null'),
$stmt->var->getAttributes()
)
),
new PhpParser\Node\Arg(
$assign_value
?: new PhpParser\Node\Expr\ConstFetch(
new PhpParser\Node\Name('null'),
$stmt->var->getAttributes()
)
),
]
);
} else {
$fake_method_call = new PhpParser\Node\Expr\MethodCall(
$stmt->var,
new PhpParser\Node\Identifier('offsetGet', $stmt->var->getAttributes()),
[
new PhpParser\Node\Arg($stmt->dim)
]
);
}
$suppressed_issues = $statements_analyzer->getSuppressedIssues();

View File

@ -1056,6 +1056,24 @@ class ArrayAssignmentTest extends TestCase
'assertions' => [],
'error_levels' => ['MixedAssignment'],
],
'implementsArrayAccessAllowNullOffset' => [
'<?php
/**
* @template-implements ArrayAccess<int, string>
*/
class C implements ArrayAccess {
public function offsetExists(int $offset) : bool { return true; }
public function offsetGet($offset) : string { return "";}
public function offsetSet(?int $offset, string $value) : void {}
public function offsetUnset(int $offset) : void { }
}
$c = new C();
$c[] = "hello";',
],
];
}
@ -1196,6 +1214,25 @@ class ArrayAssignmentTest extends TestCase
$_GET["foo"][0] = "5";',
'error_message' => 'MixedArrayAssignment',
],
'implementsArrayAccessAllowNullOffset' => [
'<?php
/**
* @template-implements ArrayAccess<int, string>
*/
class C implements ArrayAccess {
public function offsetExists(int $offset) : bool { return true; }
public function offsetGet($offset) : string { return "";}
public function offsetSet(int $offset, string $value) : void {}
public function offsetUnset(int $offset) : void { }
}
$c = new C();
$c[] = "hello";',
'error_message' => 'NullArgument',
],
];
}
}