diff --git a/config.xsd b/config.xsd index 01ad02abc..061d84db6 100644 --- a/config.xsd +++ b/config.xsd @@ -158,6 +158,8 @@ + + diff --git a/src/Psalm/Checker/Statements/Expression/CallChecker.php b/src/Psalm/Checker/Statements/Expression/CallChecker.php index 74a602d19..0a0f66210 100644 --- a/src/Psalm/Checker/Statements/Expression/CallChecker.php +++ b/src/Psalm/Checker/Statements/Expression/CallChecker.php @@ -35,6 +35,7 @@ use Psalm\Issue\ParentNotFound; use Psalm\Issue\PossiblyFalseArgument; use Psalm\Issue\PossiblyFalseReference; use Psalm\Issue\PossiblyInvalidArgument; +use Psalm\Issue\PossiblyInvalidMethodCall; use Psalm\Issue\PossiblyNullArgument; use Psalm\Issue\PossiblyNullFunctionCall; use Psalm\Issue\PossiblyNullReference; @@ -764,6 +765,9 @@ class CallChecker $non_existent_method_ids = []; $existent_method_ids = []; + $invalid_method_call_types = []; + $has_valid_method_call_type = false; + $code_location = new CodeLocation($source, $stmt); if ($class_type && is_string($stmt->name)) { @@ -782,15 +786,7 @@ class CallChecker case 'Psalm\\Type\\Atomic\\TArray': case 'Psalm\\Type\\Atomic\\TString': case 'Psalm\\Type\\Atomic\\TNumericString': - if (IssueBuffer::accepts( - new InvalidMethodCall( - 'Cannot call method ' . $stmt->name . ' on ' . $class_type . ' variable ' . $var_id, - $code_location - ), - $statements_checker->getSuppressedIssues() - )) { - return false; - } + $invalid_method_call_types[] = (string)$class_type_part; break; case 'Psalm\\Type\\Atomic\\TMixed': @@ -810,6 +806,8 @@ class CallChecker continue; } + $has_valid_method_call_type = true; + $fq_class_name = $class_type_part->value; $intersection_types = $class_type_part->getIntersectionTypes(); @@ -1030,6 +1028,32 @@ class CallChecker } } + if ($invalid_method_call_types) { + $class_type = $invalid_method_call_types[0]; + + if ($has_valid_method_call_type) { + if (IssueBuffer::accepts( + new PossiblyInvalidMethodCall( + 'Cannot call method on possible ' . $class_type . ' variable ' . $var_id, + $code_location + ), + $statements_checker->getSuppressedIssues() + )) { + return false; + } + } else { + if (IssueBuffer::accepts( + new InvalidMethodCall( + 'Cannot call method on ' . $class_type . ' variable ' . $var_id, + $code_location + ), + $statements_checker->getSuppressedIssues() + )) { + return false; + } + } + } + if ($non_existent_method_ids) { if ($existent_method_ids) { if (IssueBuffer::accepts( diff --git a/src/Psalm/Issue/PossiblyInvalidMethodCall.php b/src/Psalm/Issue/PossiblyInvalidMethodCall.php new file mode 100644 index 000000000..6413fb65d --- /dev/null +++ b/src/Psalm/Issue/PossiblyInvalidMethodCall.php @@ -0,0 +1,6 @@ + [ + 'someMethod();', + 'error_message' => 'InvalidMethodCall', + ], + 'possiblyInvalidMethodCall' => [ + 'methodOfA(); + } + }', + 'error_message' => 'PossiblyInvalidMethodCall', + ], 'selfNonStaticInvocation' => [ '