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

Always evaluate static calls when method call cannot

This commit is contained in:
Brown 2019-04-18 13:51:34 -04:00
parent 50035334d4
commit 4807ebe04a
3 changed files with 114 additions and 99 deletions

View File

@ -174,47 +174,76 @@ class MethodCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\
$no_method_id = false; $no_method_id = false;
if ($class_type) { if (!$class_type) {
$return_type = null; $class_type = Type::getMixed();
}
$lhs_types = $class_type->getTypes(); $return_type = null;
foreach ($lhs_types as $lhs_type_part) { $lhs_types = $class_type->getTypes();
$result = self::analyzeAtomicCall(
$statements_analyzer,
$stmt,
$codebase,
$context,
$lhs_type_part,
$lhs_var_id,
$return_type,
$returns_by_ref,
$has_mock,
$has_valid_method_call_type,
$has_mixed_method_call,
$invalid_method_call_types,
$existent_method_ids,
$non_existent_class_method_ids,
$non_existent_interface_method_ids
);
if ($result === false) { foreach ($lhs_types as $lhs_type_part) {
return false; $result = self::analyzeAtomicCall(
} $statements_analyzer,
$stmt,
$codebase,
$context,
$lhs_type_part,
$lhs_var_id,
$return_type,
$returns_by_ref,
$has_mock,
$has_valid_method_call_type,
$has_mixed_method_call,
$invalid_method_call_types,
$existent_method_ids,
$non_existent_class_method_ids,
$non_existent_interface_method_ids
);
if ($result === true) { if ($result === false) {
$no_method_id = true; return false;
}
} }
if ($invalid_method_call_types) { if ($result === true) {
$invalid_class_type = $invalid_method_call_types[0]; $no_method_id = true;
}
}
if ($has_valid_method_call_type || $has_mixed_method_call) { if ($invalid_method_call_types) {
$invalid_class_type = $invalid_method_call_types[0];
if ($has_valid_method_call_type || $has_mixed_method_call) {
if (IssueBuffer::accepts(
new PossiblyInvalidMethodCall(
'Cannot call method on possible ' . $invalid_class_type . ' variable ' . $lhs_var_id,
new CodeLocation($source, $stmt->name)
),
$statements_analyzer->getSuppressedIssues()
)) {
// keep going
}
} else {
if (IssueBuffer::accepts(
new InvalidMethodCall(
'Cannot call method on ' . $invalid_class_type . ' variable ' . $lhs_var_id,
new CodeLocation($source, $stmt->name)
),
$statements_analyzer->getSuppressedIssues()
)) {
// keep going
}
}
}
if ($non_existent_class_method_ids) {
if ($context->check_methods) {
if ($existent_method_ids || $has_mixed_method_call) {
if (IssueBuffer::accepts( if (IssueBuffer::accepts(
new PossiblyInvalidMethodCall( new PossiblyUndefinedMethod(
'Cannot call method on possible ' . $invalid_class_type . ' variable ' . $lhs_var_id, 'Method ' . $non_existent_class_method_ids[0] . ' does not exist',
new CodeLocation($source, $stmt->name) new CodeLocation($source, $stmt->name),
$non_existent_class_method_ids[0]
), ),
$statements_analyzer->getSuppressedIssues() $statements_analyzer->getSuppressedIssues()
)) { )) {
@ -222,9 +251,10 @@ class MethodCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\
} }
} else { } else {
if (IssueBuffer::accepts( if (IssueBuffer::accepts(
new InvalidMethodCall( new UndefinedMethod(
'Cannot call method on ' . $invalid_class_type . ' variable ' . $lhs_var_id, 'Method ' . $non_existent_class_method_ids[0] . ' does not exist',
new CodeLocation($source, $stmt->name) new CodeLocation($source, $stmt->name),
$non_existent_class_method_ids[0]
), ),
$statements_analyzer->getSuppressedIssues() $statements_analyzer->getSuppressedIssues()
)) { )) {
@ -233,75 +263,47 @@ class MethodCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\
} }
} }
if ($non_existent_class_method_ids) { return null;
if ($context->check_methods) { }
if ($existent_method_ids || $has_mixed_method_call) {
if (IssueBuffer::accepts( if ($non_existent_interface_method_ids) {
new PossiblyUndefinedMethod( if ($context->check_methods) {
'Method ' . $non_existent_class_method_ids[0] . ' does not exist', if ($existent_method_ids || $has_mixed_method_call) {
new CodeLocation($source, $stmt->name), if (IssueBuffer::accepts(
$non_existent_class_method_ids[0] new PossiblyUndefinedMethod(
), 'Method ' . $non_existent_interface_method_ids[0] . ' does not exist',
$statements_analyzer->getSuppressedIssues() new CodeLocation($source, $stmt->name),
)) { $non_existent_interface_method_ids[0]
// keep going ),
} $statements_analyzer->getSuppressedIssues()
} else { )) {
if (IssueBuffer::accepts( // keep going
new UndefinedMethod( }
'Method ' . $non_existent_class_method_ids[0] . ' does not exist', } else {
new CodeLocation($source, $stmt->name), if (IssueBuffer::accepts(
$non_existent_class_method_ids[0] new UndefinedInterfaceMethod(
), 'Method ' . $non_existent_interface_method_ids[0] . ' does not exist',
$statements_analyzer->getSuppressedIssues() new CodeLocation($source, $stmt->name),
)) { $non_existent_interface_method_ids[0]
// keep going ),
} $statements_analyzer->getSuppressedIssues()
)) {
// keep going
} }
} }
return null;
} }
if ($non_existent_interface_method_ids) { return null;
if ($context->check_methods) { }
if ($existent_method_ids || $has_mixed_method_call) {
if (IssueBuffer::accepts(
new PossiblyUndefinedMethod(
'Method ' . $non_existent_interface_method_ids[0] . ' does not exist',
new CodeLocation($source, $stmt->name),
$non_existent_interface_method_ids[0]
),
$statements_analyzer->getSuppressedIssues()
)) {
// keep going
}
} else {
if (IssueBuffer::accepts(
new UndefinedInterfaceMethod(
'Method ' . $non_existent_interface_method_ids[0] . ' does not exist',
new CodeLocation($source, $stmt->name),
$non_existent_interface_method_ids[0]
),
$statements_analyzer->getSuppressedIssues()
)) {
// keep going
}
}
}
return null; $stmt->inferredType = $return_type;
if ($returns_by_ref) {
if (!$stmt->inferredType) {
$stmt->inferredType = Type::getMixed();
} }
$stmt->inferredType = $return_type; $stmt->inferredType->by_ref = $returns_by_ref;
if ($returns_by_ref) {
if (!$stmt->inferredType) {
$stmt->inferredType = Type::getMixed();
}
$stmt->inferredType->by_ref = $returns_by_ref;
}
} }
if ($no_method_id) { if ($no_method_id) {
@ -334,7 +336,7 @@ class MethodCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\
// if we called a method on this nullable variable, remove the nullable status here // if we called a method on this nullable variable, remove the nullable status here
// because any further calls must have worked // because any further calls must have worked
if ($lhs_var_id if ($lhs_var_id
&& $class_type && !$class_type->isMixed()
&& $has_valid_method_call_type && $has_valid_method_call_type
&& !$has_mixed_method_call && !$has_mixed_method_call
&& !$invalid_method_call_types && !$invalid_method_call_types

View File

@ -370,6 +370,7 @@ class CallAnalyzer
|| $arg->value instanceof PhpParser\Node\Expr\ConstFetch || $arg->value instanceof PhpParser\Node\Expr\ConstFetch
|| $arg->value instanceof PhpParser\Node\Expr\FuncCall || $arg->value instanceof PhpParser\Node\Expr\FuncCall
|| $arg->value instanceof PhpParser\Node\Expr\MethodCall || $arg->value instanceof PhpParser\Node\Expr\MethodCall
|| $arg->value instanceof PhpParser\Node\Expr\StaticCall
|| $arg->value instanceof PhpParser\Node\Expr\Assign || $arg->value instanceof PhpParser\Node\Expr\Assign
|| $arg->value instanceof PhpParser\Node\Expr\Array_ || $arg->value instanceof PhpParser\Node\Expr\Array_
) )
@ -448,6 +449,7 @@ class CallAnalyzer
|| $arg->value instanceof PhpParser\Node\Expr\ConstFetch || $arg->value instanceof PhpParser\Node\Expr\ConstFetch
|| $arg->value instanceof PhpParser\Node\Expr\FuncCall || $arg->value instanceof PhpParser\Node\Expr\FuncCall
|| $arg->value instanceof PhpParser\Node\Expr\MethodCall || $arg->value instanceof PhpParser\Node\Expr\MethodCall
|| $arg->value instanceof PhpParser\Node\Expr\StaticCall
|| $arg->value instanceof PhpParser\Node\Expr\Assign || $arg->value instanceof PhpParser\Node\Expr\Assign
|| $arg->value instanceof PhpParser\Node\Expr\ArrayDimFetch || $arg->value instanceof PhpParser\Node\Expr\ArrayDimFetch
|| $arg->value instanceof PhpParser\Node\Expr\Array_ || $arg->value instanceof PhpParser\Node\Expr\Array_
@ -1278,6 +1280,7 @@ class CallAnalyzer
$arg->value instanceof PhpParser\Node\Expr\ConstFetch $arg->value instanceof PhpParser\Node\Expr\ConstFetch
|| $arg->value instanceof PhpParser\Node\Expr\FuncCall || $arg->value instanceof PhpParser\Node\Expr\FuncCall
|| $arg->value instanceof PhpParser\Node\Expr\MethodCall || $arg->value instanceof PhpParser\Node\Expr\MethodCall
|| $arg->value instanceof PhpParser\Node\Expr\StaticCall
) && ( ) && (
!isset($arg->value->inferredType) !isset($arg->value->inferredType)
|| !$arg->value->inferredType->by_ref || !$arg->value->inferredType->by_ref

View File

@ -577,6 +577,16 @@ class MethodCallTest extends TestCase
}', }',
'error_message' => 'UndefinedClass', 'error_message' => 'UndefinedClass',
], ],
'checkMixedMethodCallStaticMethodCallArg' => [
'<?php
class B {}
/** @param mixed $a */
function foo($a) : void {
/** @psalm-suppress MixedMethodCall */
$a->bar(B::bat());
}',
'error_message' => 'UndefinedMethod',
],
]; ];
} }
} }