mirror of
https://github.com/danog/psalm.git
synced 2025-01-21 21:31:13 +01:00
Fix #2920 - track unevaluated code after nested returns
This commit is contained in:
parent
af9e362c80
commit
6c7f89b0ab
@ -352,6 +352,11 @@ class Context
|
||||
*/
|
||||
public $error_suppressing = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $has_returned = false;
|
||||
|
||||
/**
|
||||
* @param string|null $self
|
||||
*/
|
||||
|
@ -496,6 +496,10 @@ class IfAnalyzer
|
||||
$context->possibly_assigned_var_ids += $if_scope->possibly_assigned_var_ids;
|
||||
}
|
||||
|
||||
if (!in_array(ScopeAnalyzer::ACTION_NONE, $if_scope->final_actions, true)) {
|
||||
$context->has_returned = true;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -277,6 +277,8 @@ class LoopAnalyzer
|
||||
}
|
||||
}
|
||||
|
||||
$inner_context->has_returned = false;
|
||||
|
||||
if ($inner_context->collect_references) {
|
||||
foreach ($inner_context->unreferenced_vars as $var_id => $locations) {
|
||||
if (!isset($pre_outer_context->vars_in_scope[$var_id])) {
|
||||
|
@ -85,6 +85,8 @@ class TryAnalyzer
|
||||
return false;
|
||||
}
|
||||
|
||||
$context->has_returned = false;
|
||||
|
||||
$stmt_control_actions = ScopeAnalyzer::getFinalControlActions(
|
||||
$stmt->stmts,
|
||||
$statements_analyzer->node_data,
|
||||
@ -168,6 +170,7 @@ class TryAnalyzer
|
||||
/** @var int $i */
|
||||
foreach ($stmt->catches as $i => $catch) {
|
||||
$catch_context = clone $original_context;
|
||||
$catch_context->has_returned = false;
|
||||
|
||||
foreach ($catch_context->vars_in_scope as $var_id => $type) {
|
||||
if (!isset($old_context->vars_in_scope[$var_id])) {
|
||||
|
@ -169,8 +169,6 @@ class StatementsAnalyzer extends SourceAnalyzer implements StatementsSource
|
||||
Context $global_context = null,
|
||||
$root_scope = false
|
||||
) {
|
||||
$has_returned = false;
|
||||
|
||||
// hoist functions to the top
|
||||
foreach ($stmts as $stmt) {
|
||||
if ($stmt instanceof PhpParser\Node\Stmt\Function_) {
|
||||
@ -239,8 +237,11 @@ class StatementsAnalyzer extends SourceAnalyzer implements StatementsSource
|
||||
$ignore_variable_property = false;
|
||||
$ignore_variable_method = false;
|
||||
|
||||
if ($has_returned && !($stmt instanceof PhpParser\Node\Stmt\Nop) &&
|
||||
!($stmt instanceof PhpParser\Node\Stmt\InlineHTML)
|
||||
if ($context->has_returned
|
||||
&& !$context->collect_initializations
|
||||
&& !$context->collect_mutations
|
||||
&& !($stmt instanceof PhpParser\Node\Stmt\Nop)
|
||||
&& !($stmt instanceof PhpParser\Node\Stmt\InlineHTML)
|
||||
) {
|
||||
if ($context->collect_references) {
|
||||
if (IssueBuffer::accepts(
|
||||
@ -380,10 +381,10 @@ class StatementsAnalyzer extends SourceAnalyzer implements StatementsSource
|
||||
} elseif ($stmt instanceof PhpParser\Node\Stmt\Unset_) {
|
||||
$this->analyzeUnset($stmt, $context);
|
||||
} elseif ($stmt instanceof PhpParser\Node\Stmt\Return_) {
|
||||
$has_returned = true;
|
||||
$context->has_returned = true;
|
||||
ReturnAnalyzer::analyze($this, $stmt, $context);
|
||||
} elseif ($stmt instanceof PhpParser\Node\Stmt\Throw_) {
|
||||
$has_returned = true;
|
||||
$context->has_returned = true;
|
||||
ThrowAnalyzer::analyze($this, $stmt, $context);
|
||||
} elseif ($stmt instanceof PhpParser\Node\Stmt\Switch_) {
|
||||
SwitchAnalyzer::analyze($this, $stmt, $context);
|
||||
@ -483,7 +484,7 @@ class StatementsAnalyzer extends SourceAnalyzer implements StatementsSource
|
||||
}
|
||||
}
|
||||
|
||||
$has_returned = true;
|
||||
$context->has_returned = true;
|
||||
} elseif ($stmt instanceof PhpParser\Node\Stmt\Continue_) {
|
||||
$loop_scope = $context->loop_scope;
|
||||
|
||||
@ -575,7 +576,7 @@ class StatementsAnalyzer extends SourceAnalyzer implements StatementsSource
|
||||
}
|
||||
}
|
||||
|
||||
$has_returned = true;
|
||||
$context->has_returned = true;
|
||||
} elseif ($stmt instanceof PhpParser\Node\Stmt\Static_) {
|
||||
$this->analyzeStatic($stmt, $context);
|
||||
} elseif ($stmt instanceof PhpParser\Node\Stmt\Echo_) {
|
||||
@ -865,7 +866,7 @@ class StatementsAnalyzer extends SourceAnalyzer implements StatementsSource
|
||||
}
|
||||
}
|
||||
} elseif ($stmt instanceof PhpParser\Node\Stmt\HaltCompiler) {
|
||||
$has_returned = true;
|
||||
$context->has_returned = true;
|
||||
} else {
|
||||
if (IssueBuffer::accepts(
|
||||
new UnrecognizedStatement(
|
||||
@ -882,7 +883,7 @@ class StatementsAnalyzer extends SourceAnalyzer implements StatementsSource
|
||||
&& $context->loop_scope->final_actions
|
||||
&& !in_array(ScopeAnalyzer::ACTION_NONE, $context->loop_scope->final_actions, true)
|
||||
) {
|
||||
//$has_returned = true;
|
||||
//$context->has_returned = true;
|
||||
}
|
||||
|
||||
if ($plugin_classes) {
|
||||
|
@ -990,6 +990,36 @@ class UnusedCodeTest extends TestCase
|
||||
}',
|
||||
'error_message' => 'UnusedClass',
|
||||
],
|
||||
'returnInBothIfConditions' => [
|
||||
'<?php
|
||||
|
||||
function doAThing(): bool {
|
||||
if (rand(0, 1)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}',
|
||||
'error_message' => 'UnevaluatedCode',
|
||||
],
|
||||
'unevaluatedCodeAfterReturnInFinally' => [
|
||||
'<?php
|
||||
function noOp(): void {
|
||||
return;
|
||||
}
|
||||
|
||||
function doAThing(): bool {
|
||||
try {
|
||||
noOp();
|
||||
} finally {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}',
|
||||
'error_message' => 'UnevaluatedCode',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user