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;
|
namespace Psalm\Checker\Statements\Block;
|
||||||
|
|
||||||
use PhpParser;
|
use PhpParser;
|
||||||
|
use Psalm\Checker\ScopeChecker;
|
||||||
use Psalm\Checker\Statements\ExpressionChecker;
|
use Psalm\Checker\Statements\ExpressionChecker;
|
||||||
use Psalm\Checker\StatementsChecker;
|
use Psalm\Checker\StatementsChecker;
|
||||||
use Psalm\Context;
|
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 = clone $context;
|
||||||
$for_context->inside_loop = true;
|
$for_context->inside_loop = true;
|
||||||
|
|
||||||
|
$loop_scope = new LoopScope($for_context, $context);
|
||||||
|
|
||||||
LoopChecker::analyze(
|
LoopChecker::analyze(
|
||||||
$statements_checker,
|
$statements_checker,
|
||||||
$stmt->stmts,
|
$stmt->stmts,
|
||||||
$stmt->cond,
|
$stmt->cond,
|
||||||
$stmt->loop,
|
$stmt->loop,
|
||||||
new LoopScope($for_context, $context)
|
$loop_scope,
|
||||||
|
$inner_loop_context
|
||||||
);
|
);
|
||||||
|
|
||||||
$context->vars_possibly_in_scope = array_merge(
|
if ($inner_loop_context && $while_true) {
|
||||||
$for_context->vars_possibly_in_scope,
|
// if we actually leave the loop
|
||||||
$context->vars_possibly_in_scope
|
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(
|
$context->referenced_var_ids = array_merge(
|
||||||
$for_context->referenced_var_ids,
|
$for_context->referenced_var_ids,
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
namespace Psalm\Checker\Statements\Block;
|
namespace Psalm\Checker\Statements\Block;
|
||||||
|
|
||||||
use PhpParser;
|
use PhpParser;
|
||||||
|
use Psalm\Checker\ScopeChecker;
|
||||||
use Psalm\Checker\StatementsChecker;
|
use Psalm\Checker\StatementsChecker;
|
||||||
use Psalm\Context;
|
use Psalm\Context;
|
||||||
use Psalm\Scope\LoopScope;
|
use Psalm\Scope\LoopScope;
|
||||||
@ -20,20 +21,55 @@ class WhileChecker
|
|||||||
PhpParser\Node\Stmt\While_ $stmt,
|
PhpParser\Node\Stmt\While_ $stmt,
|
||||||
Context $context
|
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;
|
$while_context = clone $context;
|
||||||
|
|
||||||
|
$loop_scope = new LoopScope($while_context, $context);
|
||||||
|
|
||||||
LoopChecker::analyze(
|
LoopChecker::analyze(
|
||||||
$statements_checker,
|
$statements_checker,
|
||||||
$stmt->stmts,
|
$stmt->stmts,
|
||||||
$stmt->cond ? [$stmt->cond] : [],
|
$stmt->cond ? [$stmt->cond] : [],
|
||||||
[],
|
[],
|
||||||
new LoopScope($while_context, $context)
|
$loop_scope,
|
||||||
|
$inner_loop_context
|
||||||
);
|
);
|
||||||
|
|
||||||
$context->vars_possibly_in_scope = array_merge(
|
if ($inner_loop_context && $while_true) {
|
||||||
$context->vars_possibly_in_scope,
|
// if we actually leave the loop
|
||||||
$while_context->vars_possibly_in_scope
|
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 = array_merge(
|
||||||
$context->referenced_var_ids,
|
$context->referenced_var_ids,
|
||||||
|
@ -764,6 +764,26 @@ class LoopScopeTest extends TestCase
|
|||||||
'MixedAssignment', 'MixedArrayAccess',
|
'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',
|
'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