mirror of
https://github.com/danog/psalm.git
synced 2024-11-26 20:34:47 +01:00
parent
6c84fecce9
commit
2a7b48ce5f
@ -2,6 +2,7 @@
|
||||
namespace Psalm\Checker\Statements\Block;
|
||||
|
||||
use PhpParser;
|
||||
use Psalm\Checker\ScopeChecker;
|
||||
use Psalm\Checker\Statements\ExpressionChecker;
|
||||
use Psalm\Checker\StatementsChecker;
|
||||
use Psalm\Context;
|
||||
@ -27,21 +28,50 @@ class ForChecker
|
||||
}
|
||||
}
|
||||
|
||||
$while_true = !$stmt->cond && !$stmt->init && !$stmt->loop;
|
||||
|
||||
$pre_context = $while_true ? clone $context : null;
|
||||
|
||||
$for_context = clone $context;
|
||||
$for_context->inside_loop = true;
|
||||
|
||||
$loop_scope = new LoopScope($for_context, $context);
|
||||
|
||||
LoopChecker::analyze(
|
||||
$statements_checker,
|
||||
$stmt->stmts,
|
||||
$stmt->cond,
|
||||
$stmt->loop,
|
||||
new LoopScope($for_context, $context)
|
||||
$loop_scope,
|
||||
$inner_loop_context
|
||||
);
|
||||
|
||||
$context->vars_possibly_in_scope = array_merge(
|
||||
$for_context->vars_possibly_in_scope,
|
||||
$context->vars_possibly_in_scope
|
||||
);
|
||||
if ($inner_loop_context && $while_true) {
|
||||
// if we actually leave the loop
|
||||
if (in_array(ScopeChecker::ACTION_BREAK, $loop_scope->final_actions, true)
|
||||
|| in_array(ScopeChecker::ACTION_END, $loop_scope->final_actions, true)
|
||||
) {
|
||||
foreach ($inner_loop_context->vars_in_scope as $var_id => $type) {
|
||||
if (!isset($context->vars_in_scope[$var_id])) {
|
||||
$context->vars_in_scope[$var_id] = $type;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$while_true
|
||||
|| in_array(ScopeChecker::ACTION_BREAK, $loop_scope->final_actions, true)
|
||||
|| in_array(ScopeChecker::ACTION_END, $loop_scope->final_actions, true)
|
||||
|| !$pre_context
|
||||
) {
|
||||
$context->vars_possibly_in_scope = array_merge(
|
||||
$context->vars_possibly_in_scope,
|
||||
$for_context->vars_possibly_in_scope
|
||||
);
|
||||
} else {
|
||||
$context->vars_in_scope = $pre_context->vars_in_scope;
|
||||
$context->vars_possibly_in_scope = $pre_context->vars_possibly_in_scope;
|
||||
}
|
||||
|
||||
$context->referenced_var_ids = array_merge(
|
||||
$for_context->referenced_var_ids,
|
||||
|
@ -2,6 +2,7 @@
|
||||
namespace Psalm\Checker\Statements\Block;
|
||||
|
||||
use PhpParser;
|
||||
use Psalm\Checker\ScopeChecker;
|
||||
use Psalm\Checker\StatementsChecker;
|
||||
use Psalm\Context;
|
||||
use Psalm\Scope\LoopScope;
|
||||
@ -20,20 +21,55 @@ class WhileChecker
|
||||
PhpParser\Node\Stmt\While_ $stmt,
|
||||
Context $context
|
||||
) {
|
||||
$while_true = $stmt->cond
|
||||
&& ($stmt->cond instanceof PhpParser\Node\Expr\ConstFetch && $stmt->cond->name->parts === ['true'])
|
||||
|| ($stmt->cond instanceof PhpParser\Node\Scalar\LNumber && $stmt->cond->value > 0);
|
||||
|
||||
$pre_context = null;
|
||||
|
||||
if ($while_true) {
|
||||
$pre_context = clone $context;
|
||||
}
|
||||
|
||||
$while_context = clone $context;
|
||||
|
||||
$loop_scope = new LoopScope($while_context, $context);
|
||||
|
||||
LoopChecker::analyze(
|
||||
$statements_checker,
|
||||
$stmt->stmts,
|
||||
$stmt->cond ? [$stmt->cond] : [],
|
||||
[],
|
||||
new LoopScope($while_context, $context)
|
||||
$loop_scope,
|
||||
$inner_loop_context
|
||||
);
|
||||
|
||||
$context->vars_possibly_in_scope = array_merge(
|
||||
$context->vars_possibly_in_scope,
|
||||
$while_context->vars_possibly_in_scope
|
||||
);
|
||||
if ($inner_loop_context && $while_true) {
|
||||
// if we actually leave the loop
|
||||
if (in_array(ScopeChecker::ACTION_BREAK, $loop_scope->final_actions, true)
|
||||
|| in_array(ScopeChecker::ACTION_END, $loop_scope->final_actions, true)
|
||||
) {
|
||||
foreach ($inner_loop_context->vars_in_scope as $var_id => $type) {
|
||||
if (!isset($context->vars_in_scope[$var_id])) {
|
||||
$context->vars_in_scope[$var_id] = $type;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$while_true
|
||||
|| in_array(ScopeChecker::ACTION_BREAK, $loop_scope->final_actions, true)
|
||||
|| in_array(ScopeChecker::ACTION_END, $loop_scope->final_actions, true)
|
||||
|| !$pre_context
|
||||
) {
|
||||
$context->vars_possibly_in_scope = array_merge(
|
||||
$context->vars_possibly_in_scope,
|
||||
$while_context->vars_possibly_in_scope
|
||||
);
|
||||
} else {
|
||||
$context->vars_in_scope = $pre_context->vars_in_scope;
|
||||
$context->vars_possibly_in_scope = $pre_context->vars_possibly_in_scope;
|
||||
}
|
||||
|
||||
$context->referenced_var_ids = array_merge(
|
||||
$context->referenced_var_ids,
|
||||
|
@ -764,6 +764,26 @@ class LoopScopeTest extends TestCase
|
||||
'MixedAssignment', 'MixedArrayAccess',
|
||||
],
|
||||
],
|
||||
'whileTrue' => [
|
||||
'<?php
|
||||
while (true) {
|
||||
$a = "hello";
|
||||
break;
|
||||
}
|
||||
while (1) {
|
||||
$b = 5;
|
||||
break;
|
||||
}
|
||||
for(;;) {
|
||||
$c = true;
|
||||
break;
|
||||
}',
|
||||
'assignments' => [
|
||||
'$a' => 'string',
|
||||
'$b' => 'int',
|
||||
'$c' => 'bool',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@ -898,6 +918,24 @@ class LoopScopeTest extends TestCase
|
||||
}',
|
||||
'error_message' => 'RedundantCondition',
|
||||
],
|
||||
'whileTrueNoBreak' => [
|
||||
'<?php
|
||||
while (true) {
|
||||
$a = "hello";
|
||||
}
|
||||
|
||||
echo $a;',
|
||||
'error_message' => 'UndefinedGlobalVariable',
|
||||
],
|
||||
'forInfiniteNoBreak' => [
|
||||
'<?php
|
||||
for (;;) {
|
||||
$a = "hello";
|
||||
}
|
||||
|
||||
echo $a;',
|
||||
'error_message' => 'UndefinedGlobalVariable',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user