mirror of
https://github.com/danog/psalm.git
synced 2024-11-26 20:34:47 +01:00
Fix #283 - add more refined PossiblyUndefinedMethod check
This commit is contained in:
parent
2f1e3652b7
commit
4f2a200b45
@ -164,6 +164,7 @@
|
||||
<xs:element name="PossiblyNullPropertyAssignment" type="IssueHandlerType" minOccurs="0" />
|
||||
<xs:element name="PossiblyNullPropertyFetch" type="IssueHandlerType" minOccurs="0" />
|
||||
<xs:element name="PossiblyNullReference" type="IssueHandlerType" minOccurs="0" />
|
||||
<xs:element name="PossiblyUndefinedMethod" type="IssueHandlerType" minOccurs="0" />
|
||||
<xs:element name="PossiblyUndefinedVariable" type="IssueHandlerType" minOccurs="0" />
|
||||
<xs:element name="PossiblyUnusedVariable" type="IssueHandlerType" minOccurs="0" />
|
||||
<xs:element name="PossiblyUnusedMethod" type="IssueHandlerType" minOccurs="0" />
|
||||
|
@ -38,6 +38,7 @@ use Psalm\Issue\PossiblyInvalidArgument;
|
||||
use Psalm\Issue\PossiblyNullArgument;
|
||||
use Psalm\Issue\PossiblyNullFunctionCall;
|
||||
use Psalm\Issue\PossiblyNullReference;
|
||||
use Psalm\Issue\PossiblyUndefinedMethod;
|
||||
use Psalm\Issue\TooFewArguments;
|
||||
use Psalm\Issue\TooManyArguments;
|
||||
use Psalm\Issue\TypeCoercion;
|
||||
@ -760,6 +761,11 @@ class CallChecker
|
||||
$config = Config::getInstance();
|
||||
$project_checker = $statements_checker->getFileChecker()->project_checker;
|
||||
|
||||
$non_existent_method_ids = [];
|
||||
$existent_method_ids = [];
|
||||
|
||||
$code_location = new CodeLocation($source, $stmt);
|
||||
|
||||
if ($class_type && is_string($stmt->name)) {
|
||||
$return_type = null;
|
||||
|
||||
@ -779,7 +785,7 @@ class CallChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidMethodCall(
|
||||
'Cannot call method ' . $stmt->name . ' on ' . $class_type . ' variable ' . $var_id,
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
$code_location
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -792,7 +798,7 @@ class CallChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new MixedMethodCall(
|
||||
'Cannot call method ' . $stmt->name . ' on a mixed variable ' . $var_id,
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
$code_location
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -829,7 +835,7 @@ class CallChecker
|
||||
$does_class_exist = ClassLikeChecker::checkFullyQualifiedClassLikeName(
|
||||
$project_checker,
|
||||
$fq_class_name,
|
||||
new CodeLocation($statements_checker->getSource(), $stmt),
|
||||
$code_location,
|
||||
$statements_checker->getSuppressedIssues()
|
||||
);
|
||||
}
|
||||
@ -842,7 +848,7 @@ class CallChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new UndefinedMethod(
|
||||
$fq_class_name . ' has no defined methods',
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
$code_location
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -893,17 +899,13 @@ class CallChecker
|
||||
|
||||
$cased_method_id = $fq_class_name . '::' . $stmt->name;
|
||||
|
||||
$does_method_exist = MethodChecker::checkMethodExists(
|
||||
$project_checker,
|
||||
$cased_method_id,
|
||||
new CodeLocation($statements_checker->getSource(), $stmt),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
);
|
||||
|
||||
if (!$does_method_exist) {
|
||||
return $does_method_exist;
|
||||
if (!MethodChecker::methodExists($project_checker, $method_id, $code_location)) {
|
||||
$non_existent_method_ids[] = $method_id;
|
||||
continue;
|
||||
}
|
||||
|
||||
$existent_method_ids[] = $method_id;
|
||||
|
||||
$class_template_params = null;
|
||||
|
||||
if ($stmt->var instanceof PhpParser\Node\Expr\Variable &&
|
||||
@ -945,7 +947,7 @@ class CallChecker
|
||||
$stmt->args,
|
||||
$class_template_params,
|
||||
$context,
|
||||
new CodeLocation($source, $stmt),
|
||||
$code_location,
|
||||
$statements_checker
|
||||
) === false) {
|
||||
return false;
|
||||
@ -961,7 +963,7 @@ class CallChecker
|
||||
$method_id,
|
||||
$context->self,
|
||||
$statements_checker->getSource(),
|
||||
new CodeLocation($source, $stmt),
|
||||
$code_location,
|
||||
$statements_checker->getSuppressedIssues()
|
||||
) === false) {
|
||||
return false;
|
||||
@ -970,7 +972,7 @@ class CallChecker
|
||||
if (MethodChecker::checkMethodNotDeprecated(
|
||||
$project_checker,
|
||||
$method_id,
|
||||
new CodeLocation($source, $stmt),
|
||||
$code_location,
|
||||
$statements_checker->getSuppressedIssues()
|
||||
) === false) {
|
||||
return false;
|
||||
@ -1028,6 +1030,32 @@ class CallChecker
|
||||
}
|
||||
}
|
||||
|
||||
if ($non_existent_method_ids) {
|
||||
if ($existent_method_ids) {
|
||||
if (IssueBuffer::accepts(
|
||||
new PossiblyUndefinedMethod(
|
||||
'Method ' . $non_existent_method_ids[0] . ' does not exist',
|
||||
$code_location
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (IssueBuffer::accepts(
|
||||
new UndefinedMethod(
|
||||
'Method ' . $non_existent_method_ids[0] . ' does not exist',
|
||||
$code_location
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$stmt->inferredType = $return_type;
|
||||
}
|
||||
|
||||
|
6
src/Psalm/Issue/PossiblyUndefinedMethod.php
Normal file
6
src/Psalm/Issue/PossiblyUndefinedMethod.php
Normal file
@ -0,0 +1,6 @@
|
||||
<?php
|
||||
namespace Psalm\Issue;
|
||||
|
||||
class PossiblyUndefinedMethod extends CodeError
|
||||
{
|
||||
}
|
@ -1277,6 +1277,28 @@ class TypeTest extends TestCase
|
||||
$a->fooBar();',
|
||||
'error_message' => 'NullReference',
|
||||
],
|
||||
'possiblyUndefinedMethod' => [
|
||||
'<?php
|
||||
class A {
|
||||
public function foo() : void {}
|
||||
}
|
||||
class B {
|
||||
public function other() : void {}
|
||||
}
|
||||
|
||||
function a(bool $cond) : void {
|
||||
if ($cond) {
|
||||
$a = new A();
|
||||
} else {
|
||||
$a = new B();
|
||||
}
|
||||
|
||||
if ($cond) {
|
||||
$a->foo();
|
||||
}
|
||||
}',
|
||||
'error_message' => 'PossiblyUndefinedMethod',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user