1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-30 04:39:00 +01:00

Add more nuanced analysis of array access fetch

This commit is contained in:
Brown 2019-01-30 15:40:37 -05:00
parent 900d5fa9db
commit c195e8fd21
5 changed files with 83 additions and 32 deletions

View File

@ -200,6 +200,7 @@ class ArrayAssignmentAnalyzer
isset($child_stmt->dim->inferredType) ? $child_stmt->dim->inferredType : Type::getInt(),
true,
$array_var_id,
$context,
$child_stmts ? null : $assignment_type
);

View File

@ -147,8 +147,8 @@ class ArrayFetchAnalyzer
$used_key_type,
false,
$array_var_id,
null,
$context->inside_isset
$context,
null
);
if ($context->inside_isset
@ -241,7 +241,6 @@ class ArrayFetchAnalyzer
* @param Type\Union $offset_type
* @param bool $in_assignment
* @param null|string $array_var_id
* @param bool $inside_isset
*
* @return Type\Union
*/
@ -252,8 +251,8 @@ class ArrayFetchAnalyzer
Type\Union $offset_type,
$in_assignment,
$array_var_id,
Type\Union $replacement_type = null,
$inside_isset = false
Context $context,
Type\Union $replacement_type = null
) {
$codebase = $statements_analyzer->getCodebase();
@ -305,7 +304,7 @@ class ArrayFetchAnalyzer
return Type::getMixed();
}
if ($offset_type->isNullable() && !$offset_type->ignore_nullable_issues && !$inside_isset) {
if ($offset_type->isNullable() && !$offset_type->ignore_nullable_issues && !$context->inside_isset) {
if (IssueBuffer::accepts(
new PossiblyNullArrayOffset(
'Cannot access value on variable ' . $array_var_id
@ -346,7 +345,7 @@ class ArrayFetchAnalyzer
$array_access_type = new Type\Union([new TEmpty]);
}
} else {
if (!$inside_isset) {
if (!$context->inside_isset) {
if (IssueBuffer::accepts(
new PossiblyNullArrayAccess(
'Cannot access array value on possibly null variable ' . $array_var_id .
@ -459,7 +458,7 @@ class ArrayFetchAnalyzer
if ($array_access_type->isEmpty()
&& !$array_type->hasMixed()
&& !$in_assignment
&& !$inside_isset
&& !$context->inside_isset
) {
if (IssueBuffer::accepts(
new EmptyArrayAccess(
@ -513,7 +512,7 @@ class ArrayFetchAnalyzer
);
}
} else {
if (!$inside_isset || $type->sealed) {
if (!$context->inside_isset || $type->sealed) {
$object_like_keys = array_keys($type->properties);
if (count($object_like_keys) === 1) {
@ -547,7 +546,7 @@ class ArrayFetchAnalyzer
$type_coerced_from_scalar
);
if ($inside_isset && !$is_contained) {
if ($context->inside_isset && !$is_contained) {
$is_contained = TypeAnalyzer::canBeContainedBy(
$codebase,
$offset_type,
@ -611,7 +610,7 @@ class ArrayFetchAnalyzer
$has_valid_offset = true;
} else {
if (!$inside_isset || $type->sealed) {
if (!$context->inside_isset || $type->sealed) {
$expected_offset_types[] = (string)$generic_key_type->getId();
}
@ -683,7 +682,7 @@ class ArrayFetchAnalyzer
if ($type instanceof TMixed || $type instanceof TGenericParam || $type instanceof TEmpty) {
$codebase->analyzer->incrementMixedCount($statements_analyzer->getFilePath());
if (!$inside_isset) {
if (!$context->inside_isset) {
if ($in_assignment) {
if (IssueBuffer::accepts(
new MixedArrayAssignment(
@ -718,18 +717,71 @@ class ArrayFetchAnalyzer
continue;
}
if ($type instanceof TNamedObject) {
if (strtolower($type->value) !== 'simplexmlelement'
&& strtolower($type->value) !== 'arrayaccess'
&& (($codebase->classExists($type->value)
&& !$codebase->classImplements($type->value, 'ArrayAccess'))
|| ($codebase->interfaceExists($type->value)
&& !$codebase->interfaceExtends($type->value, 'ArrayAccess'))
if ($type instanceof TNamedObject && $stmt->dim) {
if (strtolower($type->value) === 'simplexmlelement') {
$array_access_type = Type::getMixed();
} elseif (strtolower($type->value) === 'domnodelist') {
$fake_method_call = new PhpParser\Node\Expr\MethodCall(
$stmt->var,
new PhpParser\Node\Identifier('item', $stmt->var->getAttributes()),
[
new PhpParser\Node\Arg($stmt->dim)
]
);
$suppressed_issues = $statements_analyzer->getSuppressedIssues();
if (!in_array('PossiblyInvalidMethodCall', $suppressed_issues, true)) {
$statements_analyzer->addSuppressedIssues(['PossiblyInvalidMethodCall']);
}
\Psalm\Internal\Analyzer\Statements\Expression\Call\MethodCallAnalyzer::analyze(
$statements_analyzer,
$fake_method_call,
$context
);
if (!in_array('PossiblyInvalidMethodCall', $suppressed_issues, true)) {
$statements_analyzer->removeSuppressedIssues(['PossiblyInvalidMethodCall']);
}
$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'))
)
) {
$non_array_types[] = (string)$type;
$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();
if (!in_array('PossiblyInvalidMethodCall', $suppressed_issues, true)) {
$statements_analyzer->addSuppressedIssues(['PossiblyInvalidMethodCall']);
}
\Psalm\Internal\Analyzer\Statements\Expression\Call\MethodCallAnalyzer::analyze(
$statements_analyzer,
$fake_method_call,
$context
);
if (!in_array('PossiblyInvalidMethodCall', $suppressed_issues, true)) {
$statements_analyzer->removeSuppressedIssues(['PossiblyInvalidMethodCall']);
}
$iterator_class_type = $fake_method_call->inferredType ?? null;
$array_access_type = $iterator_class_type ?: Type::getMixed();
} else {
$array_access_type = Type::getMixed();
$non_array_types[] = (string)$type;
}
} else {
$non_array_types[] = (string)$type;
@ -817,7 +869,7 @@ class ArrayFetchAnalyzer
. (is_int($key_value) ? $key_value : '\'' . $key_value . '\'');
}
if ($has_valid_offset && $inside_isset) {
if ($has_valid_offset && $context->inside_isset) {
// do nothing
} elseif ($has_valid_offset) {
if (IssueBuffer::accepts(

View File

@ -453,10 +453,6 @@ class ClassLikes
return true;
}
if ($interface_id === 'arrayaccess' && $fq_class_name === 'domnodelist') {
return true;
}
if (isset(ClassLikeAnalyzer::SPECIAL_TYPES[$interface_id])
|| isset(ClassLikeAnalyzer::SPECIAL_TYPES[$fq_class_name])
) {

View File

@ -148,15 +148,17 @@ class ArrayAccessTest extends TestCase
'<?php
$doc = new DOMDocument();
$doc->loadXML("<node key=\"value\"/>");
$doc->getElementsByTagName("node")[0];'
$e = $doc->getElementsByTagName("node")[0];',
[
'$e' => 'null|DOMElement',
]
],
'getOnArrayAcccess' => [
'<?php
function foo(ArrayAccess $a) : void {
echo $a[0];
/** @param ArrayAccess<int, string> $a */
function foo(ArrayAccess $a) : string {
return $a[0];
}',
'assertions' => [],
'error_levels' => ['MixedArgument'],
],
'mixedKeyMixedOffset' => [
'<?php

View File

@ -250,7 +250,7 @@ class MethodSignatureTest extends TestCase
/** @var int */
private $id = 1;
public function unserialize($serialized) : void
public function unserialize(string $serialized) : void
{
[
$this->id,