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:
parent
c195e8fd21
commit
2054e3753f
@ -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
|
||||
);
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -1001,6 +1001,7 @@ class BinaryOpAnalyzer
|
||||
ArrayAssignmentAnalyzer::updateArrayType(
|
||||
$statements_source,
|
||||
$left,
|
||||
$right,
|
||||
$result_type,
|
||||
$context
|
||||
);
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user