Skip to content

Commit

Permalink
Fix continue handling in loops
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Nov 18, 2017
1 parent e19c7f4 commit 9b17568
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 15 deletions.
33 changes: 33 additions & 0 deletions src/Analyser/LookForAssignsSettings.php
@@ -0,0 +1,33 @@
<?php declare(strict_types = 1);

namespace PHPStan\Analyser;

class LookForAssignsSettings
{

/** @var bool */
private $respectAllEarlyTermination;

/** @var bool */
private $respectContinue;

public function __construct(
bool $respectAllEarlyTermination,
bool $respectContinue
)
{
$this->respectAllEarlyTermination = $respectAllEarlyTermination;
$this->respectContinue = $respectContinue;
}

public function isRespectAllEarlyTermination(): bool
{
return $this->respectAllEarlyTermination;
}

public function isRespectContinue(): bool
{
return $this->respectContinue;
}

}
32 changes: 20 additions & 12 deletions src/Analyser/NodeScopeResolver.php
Expand Up @@ -380,7 +380,7 @@ private function processNode(\PhpParser\Node $node, Scope $scope, \Closure $node
$scope = $this->lookForAssignsInBranches($scope, [
new StatementList($scope, $node->stmts),
new StatementList($scope, []),
]);
], false, new LookForAssignsSettings(true, false));
$scope = $this->enterForeach($scope, $node);

$this->processNodes($node->stmts, $scope->enterFirstLevelStatements(), $nodeCallback);
Expand Down Expand Up @@ -532,7 +532,7 @@ private function processNode(\PhpParser\Node $node, Scope $scope, \Closure $node
}

if ($node->finally !== null) {
$finallyScope = $this->lookForAssignsInBranches($scopeForLookForAssignsInBranches, $statements, false, false);
$finallyScope = $this->lookForAssignsInBranches($scopeForLookForAssignsInBranches, $statements, false, new LookForAssignsSettings(false, true));

$this->processNode($node->finally, $finallyScope, $nodeCallback);
}
Expand Down Expand Up @@ -622,7 +622,7 @@ private function processNode(\PhpParser\Node $node, Scope $scope, \Closure $node
$scope = $this->lookForAssignsInBranches($scope, [
new StatementList($scope, $node->stmts),
new StatementList($scope, []),
]);
], false, new LookForAssignsSettings(true, false));
$scope = $this->lookForAssigns($scope, $node->cond, TrinaryLogic::createYes());
$scope = $scope->filterByTruthyValue($node->cond);
}
Expand Down Expand Up @@ -783,7 +783,8 @@ private function lookForEnterVariableAssign(Scope $scope, Expr $node): Scope
private function lookForAssigns(
Scope $scope,
\PhpParser\Node $node,
TrinaryLogic $certainty
TrinaryLogic $certainty,
LookForAssignsSettings $lookForAssignsSettings = null
): Scope
{
if ($node instanceof StaticVar) {
Expand Down Expand Up @@ -816,7 +817,7 @@ private function lookForAssigns(
];
$statements = array_merge($statements, $elseIfStatements);

$scope = $this->lookForAssignsInBranches($scope, $statements);
$scope = $this->lookForAssignsInBranches($scope, $statements, false, $lookForAssignsSettings);
} elseif ($node instanceof TryCatch) {
$statements = [
new StatementList($scope, $node->stmts),
Expand All @@ -828,10 +829,10 @@ private function lookForAssigns(
), $catch->stmts);
}

$scope = $this->lookForAssignsInBranches($scope, $statements);
$scope = $this->lookForAssignsInBranches($scope, $statements, false, $lookForAssignsSettings);
if ($node->finally !== null) {
foreach ($node->finally->stmts as $statement) {
$scope = $this->lookForAssigns($scope, $statement, $certainty);
$scope = $this->lookForAssigns($scope, $statement, $certainty, $lookForAssignsSettings);
}
}
} elseif ($node instanceof MethodCall || $node instanceof FuncCall || $node instanceof Expr\StaticCall) {
Expand Down Expand Up @@ -1147,16 +1148,19 @@ private function assignVariable(
* @param \PHPStan\Analyser\Scope $initialScope
* @param \PHPStan\Analyser\StatementList[] $statementsLists
* @param bool $isSwitchCase
* @param bool $respectEarlyTermination
* @param \PHPStan\Analyser\LookForAssignsSettings $lookForAssignsSettings
* @return Scope
*/
private function lookForAssignsInBranches(
Scope $initialScope,
array $statementsLists,
bool $isSwitchCase = false,
bool $respectEarlyTermination = true
LookForAssignsSettings $lookForAssignsSettings = null
): Scope
{
if ($lookForAssignsSettings === null) {
$lookForAssignsSettings = new LookForAssignsSettings(true, true);
}
/** @var \PHPStan\Analyser\Scope|null $intersectedScope */
$intersectedScope = null;

Expand All @@ -1173,11 +1177,15 @@ private function lookForAssignsInBranches(

$earlyTerminationStatement = null;
foreach ($statements as $statement) {
$branchScope = $this->lookForAssigns($branchScope, $statement, TrinaryLogic::createYes());
$branchScope = $this->lookForAssigns($branchScope, $statement, TrinaryLogic::createYes(), $lookForAssignsSettings);
$branchScopeWithInitialScopeRemoved = $branchScope->removeVariables($initialScope, false);
$earlyTerminationStatement = $this->findStatementEarlyTermination($statement, $branchScope);
if ($earlyTerminationStatement !== null) {
if (!$isSwitchCase && $respectEarlyTermination) {
if (
!$isSwitchCase
&& $lookForAssignsSettings->isRespectAllEarlyTermination()
&& (!$earlyTerminationStatement instanceof Continue_ || $lookForAssignsSettings->isRespectContinue())
) {
continue 2;
}
break;
Expand All @@ -1188,7 +1196,7 @@ private function lookForAssignsInBranches(
$earlyTerminationStatement === null
|| $earlyTerminationStatement instanceof Break_
|| $earlyTerminationStatement instanceof Continue_
|| !$respectEarlyTermination
|| !$lookForAssignsSettings->isRespectAllEarlyTermination()
) {
if ($intersectedScope === null) {
$intersectedScope = $initialScope->createIntersectedScope($branchScopeWithInitialScopeRemoved);
Expand Down
6 changes: 3 additions & 3 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Expand Up @@ -3705,11 +3705,11 @@ public function testResolveStatic(
public function dataLoopVariables(): array
{
return [
/*[
'LoopVariables\Lorem|LoopVariables\Foo|null',
[
'LoopVariables\Foo|LoopVariables\Lorem|null',
'$foo',
"'begin';",
],*/
],
[
'LoopVariables\Foo',
'$foo',
Expand Down

0 comments on commit 9b17568

Please sign in to comment.