2020-05-19 18:56:23 +02:00
|
|
|
<?php
|
|
|
|
namespace Psalm\Internal\Analyzer\Statements;
|
|
|
|
|
|
|
|
use PhpParser;
|
|
|
|
use Psalm\CodeLocation;
|
|
|
|
use Psalm\Context;
|
2021-06-08 04:55:21 +02:00
|
|
|
use Psalm\Internal\Analyzer\ScopeAnalyzer;
|
|
|
|
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
2020-05-19 18:56:23 +02:00
|
|
|
use Psalm\Issue\ContinueOutsideLoop;
|
|
|
|
use Psalm\IssueBuffer;
|
|
|
|
use Psalm\Type;
|
|
|
|
|
|
|
|
class ContinueAnalyzer
|
|
|
|
{
|
|
|
|
public static function analyze(
|
|
|
|
StatementsAnalyzer $statements_analyzer,
|
|
|
|
PhpParser\Node\Stmt\Continue_ $stmt,
|
|
|
|
Context $context
|
2021-06-10 18:09:46 +02:00
|
|
|
): void {
|
2020-10-24 18:23:59 +02:00
|
|
|
$count = $stmt->num
|
|
|
|
&& $stmt->num instanceof PhpParser\Node\Scalar\LNumber
|
|
|
|
? $stmt->num->value
|
|
|
|
: 1;
|
|
|
|
|
2020-05-19 18:56:23 +02:00
|
|
|
$loop_scope = $context->loop_scope;
|
|
|
|
|
2020-10-24 18:23:59 +02:00
|
|
|
if ($count === 2 && isset($loop_scope->loop_parent_context->loop_scope)) {
|
|
|
|
$loop_scope = $loop_scope->loop_parent_context->loop_scope;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($count === 3 && isset($loop_scope->loop_parent_context->loop_scope)) {
|
|
|
|
$loop_scope = $loop_scope->loop_parent_context->loop_scope;
|
|
|
|
}
|
|
|
|
|
2020-05-19 18:56:23 +02:00
|
|
|
if ($loop_scope === null) {
|
|
|
|
if (!$context->break_types) {
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
new ContinueOutsideLoop(
|
|
|
|
'Continue call outside loop context',
|
|
|
|
new CodeLocation($statements_analyzer, $stmt)
|
|
|
|
),
|
|
|
|
$statements_analyzer->getSource()->getSuppressedIssues()
|
|
|
|
)) {
|
2021-06-10 18:09:46 +02:00
|
|
|
return;
|
2020-05-19 18:56:23 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if ($context->break_types
|
|
|
|
&& \end($context->break_types) === 'switch'
|
2020-10-24 18:23:59 +02:00
|
|
|
&& $count < 2
|
2020-05-19 18:56:23 +02:00
|
|
|
) {
|
|
|
|
$loop_scope->final_actions[] = ScopeAnalyzer::ACTION_LEAVE_SWITCH;
|
|
|
|
} else {
|
|
|
|
$loop_scope->final_actions[] = ScopeAnalyzer::ACTION_CONTINUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
$redefined_vars = $context->getRedefinedVars($loop_scope->loop_parent_context->vars_in_scope);
|
|
|
|
|
|
|
|
if ($loop_scope->redefined_loop_vars === null) {
|
|
|
|
$loop_scope->redefined_loop_vars = $redefined_vars;
|
|
|
|
} else {
|
|
|
|
foreach ($loop_scope->redefined_loop_vars as $redefined_var => $type) {
|
|
|
|
if (!isset($redefined_vars[$redefined_var])) {
|
|
|
|
unset($loop_scope->redefined_loop_vars[$redefined_var]);
|
|
|
|
} else {
|
|
|
|
$loop_scope->redefined_loop_vars[$redefined_var] = Type::combineUnionTypes(
|
|
|
|
$redefined_vars[$redefined_var],
|
|
|
|
$type
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($redefined_vars as $var => $type) {
|
2021-05-13 15:39:07 +02:00
|
|
|
if (isset($loop_scope->possibly_redefined_loop_vars[$var])) {
|
2020-05-19 18:56:23 +02:00
|
|
|
$loop_scope->possibly_redefined_loop_vars[$var] = Type::combineUnionTypes(
|
|
|
|
$type,
|
|
|
|
$loop_scope->possibly_redefined_loop_vars[$var]
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
$loop_scope->possibly_redefined_loop_vars[$var] = $type;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-21 21:16:19 +02:00
|
|
|
if ($context->finally_scope) {
|
|
|
|
foreach ($context->vars_in_scope as $var_id => $type) {
|
|
|
|
if (isset($context->finally_scope->vars_in_scope[$var_id])) {
|
|
|
|
if ($context->finally_scope->vars_in_scope[$var_id] !== $type) {
|
|
|
|
$context->finally_scope->vars_in_scope[$var_id] = Type::combineUnionTypes(
|
|
|
|
$context->finally_scope->vars_in_scope[$var_id],
|
|
|
|
$type,
|
|
|
|
$statements_analyzer->getCodebase()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$context->finally_scope->vars_in_scope[$var_id] = $type;
|
2020-11-25 20:03:05 +01:00
|
|
|
$type->possibly_undefined = true;
|
|
|
|
$type->possibly_undefined_from_try = true;
|
2020-09-21 21:16:19 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-05-19 18:56:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$context->has_returned = true;
|
|
|
|
}
|
|
|
|
}
|