diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/AssignmentAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/AssignmentAnalyzer.php index f01cc7a30..7c5e7a5bc 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/AssignmentAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/AssignmentAnalyzer.php @@ -359,14 +359,16 @@ class AssignmentAnalyzer ? 'Unable to determine the type that ' . $var_id . ' is being assigned to' : 'Unable to determine the type of this assignment'; - if ($origin_location && $origin_location->getLineNumber() === $assign_var->getLine()) { + $issue_location = new CodeLocation($statements_analyzer->getSource(), $assign_var); + + if ($origin_location && $origin_location->getHash() === $issue_location->getHash()) { $origin_location = null; } if (IssueBuffer::accepts( new MixedAssignment( $message, - new CodeLocation($statements_analyzer->getSource(), $assign_var), + $issue_location, $origin_location ), $statements_analyzer->getSuppressedIssues() diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php index 938b246e0..890c35268 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php @@ -14,6 +14,7 @@ use Psalm\Internal\Type\Comparator\CallableTypeComparator; use Psalm\Internal\Type\Comparator\UnionTypeComparator; use Psalm\Internal\DataFlow\DataFlowNode; use Psalm\Internal\Codebase\TaintFlowGraph; +use Psalm\Internal\Codebase\VariableUseGraph; use Psalm\Internal\MethodIdentifier; use Psalm\Internal\Scanner\UnresolvedConstantComponent; use Psalm\Internal\Type\TemplateBound; @@ -611,13 +612,31 @@ class ArgumentAnalyzer $codebase->analyzer->incrementMixedCount($statements_analyzer->getFilePath()); } + $origin_locations = []; + + if ($statements_analyzer->data_flow_graph instanceof VariableUseGraph) { + foreach ($input_type->parent_nodes as $parent_node) { + $origin_locations = array_merge( + $origin_locations, + $statements_analyzer->data_flow_graph->getOriginLocations($parent_node) + ); + } + } + + $origin_location = count($origin_locations) === 1 ? reset($origin_locations) : null; + + if ($origin_location && $origin_location->getHash() === $arg_location->getHash()) { + $origin_location = null; + } + if (IssueBuffer::accepts( new MixedArgument( 'Argument ' . ($argument_offset + 1) . $method_identifier . ' cannot be ' . $input_type->getId() . ', expecting ' . $param_type, $arg_location, - $cased_method_id + $cased_method_id, + $origin_location ), $statements_analyzer->getSuppressedIssues() )) { diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicMethodCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicMethodCallAnalyzer.php index 0d269bf8a..7b0942f0b 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicMethodCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicMethodCallAnalyzer.php @@ -11,6 +11,7 @@ use Psalm\Internal\Analyzer\Statements\Expression\Call\ClassTemplateParamCollect use Psalm\Internal\Analyzer\Statements\Expression\Call\ArgumentsAnalyzer; use Psalm\Internal\Analyzer\StatementsAnalyzer; use Psalm\Internal\Codebase\InternalCallMapHandler; +use Psalm\Internal\Codebase\VariableUseGraph; use Psalm\Codebase; use Psalm\CodeLocation; use Psalm\Context; @@ -44,6 +45,7 @@ class AtomicMethodCallAnalyzer extends CallAnalyzer PhpParser\Node\Expr\MethodCall $stmt, Codebase $codebase, Context $context, + Type\Union $lhs_type, Type\Atomic $lhs_type_part, ?Type\Atomic $static_type, bool $is_intersection, @@ -80,6 +82,7 @@ class AtomicMethodCallAnalyzer extends CallAnalyzer $statements_analyzer, $codebase, $stmt, + $lhs_type, $lhs_type_part, $lhs_var_id, $context, @@ -269,6 +272,7 @@ class AtomicMethodCallAnalyzer extends CallAnalyzer $stmt, $codebase, $context, + $lhs_type, $lhs_type_part, $lhs_var_id, $result, @@ -451,6 +455,7 @@ class AtomicMethodCallAnalyzer extends CallAnalyzer PhpParser\Node\Expr\MethodCall $stmt, Codebase $codebase, Context $context, + Type\Union $lhs_type, Type\Atomic $lhs_type_part, ?string $lhs_var_id, AtomicMethodCallAnalysisResult $result, @@ -470,6 +475,7 @@ class AtomicMethodCallAnalyzer extends CallAnalyzer $stmt, $codebase, $context, + $lhs_type, $intersection_type, $lhs_type_part, true, @@ -544,6 +550,7 @@ class AtomicMethodCallAnalyzer extends CallAnalyzer StatementsAnalyzer $statements_analyzer, Codebase $codebase, PhpParser\Node\Expr\MethodCall $stmt, + Type\Union $lhs_type, Type\Atomic $lhs_type_part, ?string $lhs_var_id, Context $context, @@ -600,10 +607,30 @@ class AtomicMethodCallAnalyzer extends CallAnalyzer } } + $origin_locations = []; + + if ($statements_analyzer->data_flow_graph instanceof VariableUseGraph) { + foreach ($lhs_type->parent_nodes as $parent_node) { + $origin_locations = array_merge( + $origin_locations, + $statements_analyzer->data_flow_graph->getOriginLocations($parent_node) + ); + } + } + + $origin_location = count($origin_locations) === 1 ? reset($origin_locations) : null; + + $name_code_location = new CodeLocation($statements_analyzer, $stmt->name); + + if ($origin_location && $origin_location->getHash() === $name_code_location->getHash()) { + $origin_location = null; + } + if (IssueBuffer::accepts( new MixedMethodCall( $message, - new CodeLocation($statements_analyzer, $stmt->name) + $name_code_location, + $origin_location ), $statements_analyzer->getSuppressedIssues() )) { diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/MethodCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/MethodCallAnalyzer.php index dd11e0c05..e0c304985 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/MethodCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/MethodCallAnalyzer.php @@ -185,6 +185,7 @@ class MethodCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\ $stmt, $codebase, $context, + $class_type, $lhs_type_part, $lhs_type_part instanceof Type\Atomic\TNamedObject || $lhs_type_part instanceof Type\Atomic\TTemplateParam diff --git a/src/Psalm/Internal/Analyzer/Statements/ReturnAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/ReturnAnalyzer.php index 102ce848f..87bcfe701 100644 --- a/src/Psalm/Internal/Analyzer/Statements/ReturnAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/ReturnAnalyzer.php @@ -16,6 +16,7 @@ use Psalm\Context; use Psalm\Exception\DocblockParseException; use Psalm\Internal\DataFlow\DataFlowNode; use Psalm\Internal\Codebase\TaintFlowGraph; +use Psalm\Internal\Codebase\VariableUseGraph; use Psalm\Internal\Type\TemplateResult; use Psalm\Internal\Type\TemplateInferredTypeReplacer; use Psalm\Issue\FalsableReturnStatement; @@ -303,10 +304,30 @@ class ReturnAnalyzer } if ($stmt_type->isMixed()) { + $origin_locations = []; + + if ($statements_analyzer->data_flow_graph instanceof VariableUseGraph) { + foreach ($stmt_type->parent_nodes as $parent_node) { + $origin_locations = array_merge( + $origin_locations, + $statements_analyzer->data_flow_graph->getOriginLocations($parent_node) + ); + } + } + + $origin_location = count($origin_locations) === 1 ? reset($origin_locations) : null; + + $return_location = new CodeLocation($source, $stmt->expr); + + if ($origin_location && $origin_location->getHash() === $return_location->getHash()) { + $origin_location = null; + } + if (IssueBuffer::accepts( new MixedReturnStatement( 'Could not infer a return type', - new CodeLocation($source, $stmt->expr) + $return_location, + $origin_location ), $statements_analyzer->getSuppressedIssues() )) { diff --git a/tests/UnusedVariableTest.php b/tests/UnusedVariableTest.php index 9681871b3..9572815f6 100644 --- a/tests/UnusedVariableTest.php +++ b/tests/UnusedVariableTest.php @@ -3176,7 +3176,7 @@ class UnusedVariableTest extends TestCase function takesArray(array $arr) : void { foreach ($arr as $a) {} }', - 'error_message' => 'MixedAssignment - src' . DIRECTORY_SEPARATOR . 'somefile.php:3:42 - Unable to determine the type that $a is being assigned to, derived from expression at src' . DIRECTORY_SEPARATOR . 'somefile.php:2:47' + 'error_message' => 'MixedAssignment - src' . DIRECTORY_SEPARATOR . 'somefile.php:3:42 - Unable to determine the type that $a is being assigned to. Consider improving the type at src' . DIRECTORY_SEPARATOR . 'somefile.php:2:47' ], 'warnAboutOriginalBadFunctionCall' => [ ' 'MixedAssignment - src' . DIRECTORY_SEPARATOR . 'somefile.php:8:38 - Unable to determine the type that $a is being assigned to, derived from expression at src' . DIRECTORY_SEPARATOR . 'somefile.php:2:44' + 'error_message' => 'MixedAssignment - src' . DIRECTORY_SEPARATOR . 'somefile.php:8:38 - Unable to determine the type that $a is being assigned to. Consider improving the type at src' . DIRECTORY_SEPARATOR . 'somefile.php:2:44' ], 'warnAboutOriginalBadStaticCall' => [ ' 'MixedAssignment - src' . DIRECTORY_SEPARATOR . 'somefile.php:10:38 - Unable to determine the type that $a is being assigned to, derived from expression at src' . DIRECTORY_SEPARATOR . 'somefile.php:3:62' + 'error_message' => 'MixedAssignment - src' . DIRECTORY_SEPARATOR . 'somefile.php:10:38 - Unable to determine the type that $a is being assigned to. Consider improving the type at src' . DIRECTORY_SEPARATOR . 'somefile.php:3:62' ], 'warnAboutOriginalBadInstanceCall' => [ ' 'MixedAssignment - src' . DIRECTORY_SEPARATOR . 'somefile.php:10:38 - Unable to determine the type that $a is being assigned to, derived from expression at src' . DIRECTORY_SEPARATOR . 'somefile.php:3:55' + 'error_message' => 'MixedAssignment - src' . DIRECTORY_SEPARATOR . 'somefile.php:10:38 - Unable to determine the type that $a is being assigned to. Consider improving the type at src' . DIRECTORY_SEPARATOR . 'somefile.php:3:55' ], 'warnAboutDocblockReturnType' => [ ' 'MixedAssignment - src' . DIRECTORY_SEPARATOR . 'somefile.php:10:47 - Unable to determine the type that $a is being assigned to, derived from expression at src' . DIRECTORY_SEPARATOR . 'somefile.php:2:33' + 'error_message' => 'MixedAssignment - src' . DIRECTORY_SEPARATOR . 'somefile.php:10:47 - Unable to determine the type that $a is being assigned to. Consider improving the type at src' . DIRECTORY_SEPARATOR . 'somefile.php:2:33' + ], + 'warnAboutMixedArgument' => [ + ' 'MixedArgument - src' . DIRECTORY_SEPARATOR . 'somefile.php:10:30 - Argument 1 of echo cannot be mixed, expecting string. Consider improving the type at src' . DIRECTORY_SEPARATOR . 'somefile.php:2:44' + ], + 'warnAboutMixedMethodCall' => [ + 'foo(); + }', + 'error_message' => 'MixedMethodCall - src' . DIRECTORY_SEPARATOR . 'somefile.php:10:29 - Cannot determine the type of $a when calling method foo. Consider improving the type at src' . DIRECTORY_SEPARATOR . 'somefile.php:2:44' + ], + 'warnAboutMixedReturnStatement' => [ + ' 'MixedReturnStatement - src' . DIRECTORY_SEPARATOR . 'somefile.php:11:36 - Could not infer a return type. Consider improving the type at src' . DIRECTORY_SEPARATOR . 'somefile.php:2:44' ], ]; }