Skip to content

Commit ce6bef1

Browse files
authored
While loop-like fixes for other loops and if condition
1 parent a10c133 commit ce6bef1

File tree

2 files changed

+87
-57
lines changed

2 files changed

+87
-57
lines changed

phpstan-baseline.neon

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,6 @@ parameters:
4242
count: 1
4343
path: src/Analyser/MutatingScope.php
4444

45-
-
46-
message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#'
47-
identifier: phpstanApi.instanceofType
48-
count: 2
49-
path: src/Analyser/NodeScopeResolver.php
50-
5145
-
5246
message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#'
5347
identifier: phpstanApi.instanceofType

src/Analyser/NodeScopeResolver.php

Lines changed: 87 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,6 @@
171171
use PHPStan\Type\ClosureType;
172172
use PHPStan\Type\Constant\ConstantArrayType;
173173
use PHPStan\Type\Constant\ConstantArrayTypeBuilder;
174-
use PHPStan\Type\Constant\ConstantBooleanType;
175174
use PHPStan\Type\Constant\ConstantIntegerType;
176175
use PHPStan\Type\Constant\ConstantStringType;
177176
use PHPStan\Type\ErrorType;
@@ -1079,7 +1078,7 @@ private function processStmtNode(
10791078
}
10801079
} elseif ($stmt instanceof If_) {
10811080
$conditionType = ($this->treatPhpDocTypesAsCertain ? $scope->getType($stmt->cond) : $scope->getNativeType($stmt->cond))->toBoolean();
1082-
$ifAlwaysTrue = $conditionType->isTrue()->yes();
1081+
$ifAlwaysTrue = $conditionType->isTrue();
10831082
$condResult = $this->processExprNode($stmt, $stmt->cond, $scope, $nodeCallback, ExpressionContext::createDeep(), $context);
10841083
$exitPoints = [];
10851084
$throwPoints = $overridingThrowPoints ?? $condResult->getThrowPoints();
@@ -1089,47 +1088,54 @@ private function processStmtNode(
10891088
$alwaysTerminating = true;
10901089
$hasYield = $condResult->hasYield();
10911090

1092-
$branchScopeStatementResult = $this->processStmtNodes($stmt, $stmt->stmts, $condResult->getTruthyScope(), $nodeCallback, $context);
1093-
1094-
if (!$conditionType instanceof ConstantBooleanType || $conditionType->getValue()) {
1095-
$exitPoints = $branchScopeStatementResult->getExitPoints();
1096-
$throwPoints = array_merge($throwPoints, $branchScopeStatementResult->getThrowPoints());
1097-
$impurePoints = array_merge($impurePoints, $branchScopeStatementResult->getImpurePoints());
1098-
$branchScope = $branchScopeStatementResult->getScope();
1099-
$finalScope = $branchScopeStatementResult->isAlwaysTerminating() ? null : $branchScope;
1100-
$alwaysTerminating = $branchScopeStatementResult->isAlwaysTerminating();
1101-
if (count($branchScopeStatementResult->getEndStatements()) > 0) {
1102-
$endStatements = array_merge($endStatements, $branchScopeStatementResult->getEndStatements());
1103-
} elseif (count($stmt->stmts) > 0) {
1104-
$endStatements[] = new EndStatementResult($stmt->stmts[count($stmt->stmts) - 1], $branchScopeStatementResult);
1105-
} else {
1106-
$endStatements[] = new EndStatementResult($stmt, $branchScopeStatementResult);
1091+
if ($context->isTopLevel() || !$conditionType->isTrue()->no()) {
1092+
$branchScopeStatementResult = $this->processStmtNodes($stmt, $stmt->stmts, $condResult->getTruthyScope(), $nodeCallback, $context);
1093+
1094+
if (!$conditionType->isTrue()->no()) {
1095+
$exitPoints = $branchScopeStatementResult->getExitPoints();
1096+
$throwPoints = array_merge($throwPoints, $branchScopeStatementResult->getThrowPoints());
1097+
$impurePoints = array_merge($impurePoints, $branchScopeStatementResult->getImpurePoints());
1098+
$branchScope = $branchScopeStatementResult->getScope();
1099+
$finalScope = $branchScopeStatementResult->isAlwaysTerminating() ? null : $branchScope;
1100+
$alwaysTerminating = $branchScopeStatementResult->isAlwaysTerminating();
1101+
if (count($branchScopeStatementResult->getEndStatements()) > 0) {
1102+
$endStatements = array_merge($endStatements, $branchScopeStatementResult->getEndStatements());
1103+
} elseif (count($stmt->stmts) > 0) {
1104+
$endStatements[] = new EndStatementResult($stmt->stmts[count($stmt->stmts) - 1], $branchScopeStatementResult);
1105+
} else {
1106+
$endStatements[] = new EndStatementResult($stmt, $branchScopeStatementResult);
1107+
}
1108+
$hasYield = $branchScopeStatementResult->hasYield() || $hasYield;
11071109
}
1108-
$hasYield = $branchScopeStatementResult->hasYield() || $hasYield;
11091110
}
11101111

11111112
$scope = $condResult->getFalseyScope();
1112-
$lastElseIfConditionIsTrue = false;
1113+
$lastElseIfConditionIsTrue = TrinaryLogic::createNo();
11131114

11141115
$condScope = $scope;
11151116
foreach ($stmt->elseifs as $elseif) {
11161117
$nodeCallback($elseif, $scope);
1118+
if (!$context->isTopLevel()) {
1119+
if ($ifAlwaysTrue->yes() || $lastElseIfConditionIsTrue->yes()) {
1120+
continue;
1121+
}
1122+
}
11171123
$elseIfConditionType = ($this->treatPhpDocTypesAsCertain ? $condScope->getType($elseif->cond) : $scope->getNativeType($elseif->cond))->toBoolean();
11181124
$condResult = $this->processExprNode($stmt, $elseif->cond, $condScope, $nodeCallback, ExpressionContext::createDeep(), $context);
11191125
$throwPoints = array_merge($throwPoints, $condResult->getThrowPoints());
11201126
$impurePoints = array_merge($impurePoints, $condResult->getImpurePoints());
11211127
$condScope = $condResult->getScope();
11221128
$branchScopeStatementResult = $this->processStmtNodes($elseif, $elseif->stmts, $condResult->getTruthyScope(), $nodeCallback, $context);
11231129

1130+
if (!$context->isTopLevel() && $elseIfConditionType->isTrue()->no()) {
1131+
$scope = $condScope->filterByFalseyValue($elseif->cond);
1132+
continue;
1133+
}
1134+
11241135
if (
1125-
!$ifAlwaysTrue
1126-
&& (
1127-
!$lastElseIfConditionIsTrue
1128-
&& (
1129-
!$elseIfConditionType instanceof ConstantBooleanType
1130-
|| $elseIfConditionType->getValue()
1131-
)
1132-
)
1136+
!$ifAlwaysTrue->yes()
1137+
&& !$lastElseIfConditionIsTrue->yes()
1138+
&& !$elseIfConditionType->isTrue()->no()
11331139
) {
11341140
$exitPoints = array_merge($exitPoints, $branchScopeStatementResult->getExitPoints());
11351141
$throwPoints = array_merge($throwPoints, $branchScopeStatementResult->getThrowPoints());
@@ -1147,48 +1153,48 @@ private function processStmtNode(
11471153
$hasYield = $hasYield || $branchScopeStatementResult->hasYield();
11481154
}
11491155

1150-
if (
1151-
$elseIfConditionType->isTrue()->yes()
1152-
) {
1153-
$lastElseIfConditionIsTrue = true;
1156+
if ($elseIfConditionType->isTrue()->yes()) {
1157+
$lastElseIfConditionIsTrue = $elseIfConditionType->isTrue();
11541158
}
1155-
11561159
$condScope = $condScope->filterByFalseyValue($elseif->cond);
11571160
$scope = $condScope;
11581161
}
11591162

11601163
if ($stmt->else === null) {
1161-
if (!$ifAlwaysTrue && !$lastElseIfConditionIsTrue) {
1164+
if (!$ifAlwaysTrue->yes() && !$lastElseIfConditionIsTrue->yes()) {
11621165
$finalScope = $scope->mergeWith($finalScope);
11631166
$alwaysTerminating = false;
11641167
}
11651168
} else {
11661169
$nodeCallback($stmt->else, $scope);
1167-
$branchScopeStatementResult = $this->processStmtNodes($stmt->else, $stmt->else->stmts, $scope, $nodeCallback, $context);
11681170

1169-
if (!$ifAlwaysTrue && !$lastElseIfConditionIsTrue) {
1170-
$exitPoints = array_merge($exitPoints, $branchScopeStatementResult->getExitPoints());
1171-
$throwPoints = array_merge($throwPoints, $branchScopeStatementResult->getThrowPoints());
1172-
$impurePoints = array_merge($impurePoints, $branchScopeStatementResult->getImpurePoints());
1173-
$branchScope = $branchScopeStatementResult->getScope();
1174-
$finalScope = $branchScopeStatementResult->isAlwaysTerminating() ? $finalScope : $branchScope->mergeWith($finalScope);
1175-
$alwaysTerminating = $alwaysTerminating && $branchScopeStatementResult->isAlwaysTerminating();
1176-
if (count($branchScopeStatementResult->getEndStatements()) > 0) {
1177-
$endStatements = array_merge($endStatements, $branchScopeStatementResult->getEndStatements());
1178-
} elseif (count($stmt->else->stmts) > 0) {
1179-
$endStatements[] = new EndStatementResult($stmt->else->stmts[count($stmt->else->stmts) - 1], $branchScopeStatementResult);
1180-
} else {
1181-
$endStatements[] = new EndStatementResult($stmt->else, $branchScopeStatementResult);
1171+
if ($context->isTopLevel() || (!$ifAlwaysTrue->yes() && !$lastElseIfConditionIsTrue->yes())) {
1172+
$branchScopeStatementResult = $this->processStmtNodes($stmt->else, $stmt->else->stmts, $scope, $nodeCallback, $context);
1173+
1174+
if (!$ifAlwaysTrue->yes() && !$lastElseIfConditionIsTrue->yes()) {
1175+
$exitPoints = array_merge($exitPoints, $branchScopeStatementResult->getExitPoints());
1176+
$throwPoints = array_merge($throwPoints, $branchScopeStatementResult->getThrowPoints());
1177+
$impurePoints = array_merge($impurePoints, $branchScopeStatementResult->getImpurePoints());
1178+
$branchScope = $branchScopeStatementResult->getScope();
1179+
$finalScope = $branchScopeStatementResult->isAlwaysTerminating() ? $finalScope : $branchScope->mergeWith($finalScope);
1180+
$alwaysTerminating = $alwaysTerminating && $branchScopeStatementResult->isAlwaysTerminating();
1181+
if (count($branchScopeStatementResult->getEndStatements()) > 0) {
1182+
$endStatements = array_merge($endStatements, $branchScopeStatementResult->getEndStatements());
1183+
} elseif (count($stmt->else->stmts) > 0) {
1184+
$endStatements[] = new EndStatementResult($stmt->else->stmts[count($stmt->else->stmts) - 1], $branchScopeStatementResult);
1185+
} else {
1186+
$endStatements[] = new EndStatementResult($stmt->else, $branchScopeStatementResult);
1187+
}
1188+
$hasYield = $hasYield || $branchScopeStatementResult->hasYield();
11821189
}
1183-
$hasYield = $hasYield || $branchScopeStatementResult->hasYield();
11841190
}
11851191
}
11861192

11871193
if ($finalScope === null) {
11881194
$finalScope = $scope;
11891195
}
11901196

1191-
if ($stmt->else === null && !$ifAlwaysTrue && !$lastElseIfConditionIsTrue) {
1197+
if ($stmt->else === null && !$ifAlwaysTrue->yes() && !$lastElseIfConditionIsTrue->yes()) {
11921198
$endStatements[] = new EndStatementResult($stmt, new StatementResult($finalScope, $hasYield, $alwaysTerminating, $exitPoints, $throwPoints, $impurePoints));
11931199
}
11941200

@@ -1202,6 +1208,7 @@ private function processStmtNode(
12021208
$condResult = $this->processExprNode($stmt, $stmt->expr, $scope, $nodeCallback, ExpressionContext::createDeep(), $context);
12031209
$throwPoints = $overridingThrowPoints ?? $condResult->getThrowPoints();
12041210
$impurePoints = $condResult->getImpurePoints();
1211+
$exprType = $scope->getType($stmt->expr);
12051212
$scope = $condResult->getScope();
12061213
$arrayComparisonExpr = new BinaryOp\NotIdentical(
12071214
$stmt->expr,
@@ -1214,6 +1221,18 @@ private function processStmtNode(
12141221
$originalScope = $scope;
12151222
$bodyScope = $scope;
12161223

1224+
$isIterableAtLeastOnce = $exprType->isIterableAtLeastOnce();
1225+
if (!$context->isTopLevel() && $isIterableAtLeastOnce->no()) {
1226+
return new StatementResult(
1227+
$scope,
1228+
$condResult->hasYield(),
1229+
$condResult->isAlwaysTerminating(),
1230+
[],
1231+
$throwPoints,
1232+
$impurePoints,
1233+
);
1234+
}
1235+
12171236
if ($context->isTopLevel()) {
12181237
$originalScope = $this->polluteScopeWithAlwaysIterableForeach ? $scope->filterByTruthyValue($arrayComparisonExpr) : $scope;
12191238
$bodyScope = $this->enterForeach($originalScope, $originalScope, $stmt);
@@ -1250,8 +1269,6 @@ private function processStmtNode(
12501269
$finalScope = $breakExitPoint->getScope()->mergeWith($finalScope);
12511270
}
12521271

1253-
$exprType = $scope->getType($stmt->expr);
1254-
$isIterableAtLeastOnce = $exprType->isIterableAtLeastOnce();
12551272
if ($exprType->isIterable()->no() || $isIterableAtLeastOnce->maybe()) {
12561273
$finalScope = $finalScope->mergeWith($scope->filterByTruthyValue(new BooleanOr(
12571274
new BinaryOp\Identical(
@@ -1494,6 +1511,25 @@ private function processStmtNode(
14941511
$bodyScope = $condResult->getTruthyScope();
14951512
}
14961513

1514+
if (!$context->isTopLevel() && $isIterableAtLeastOnce->no()) {
1515+
if (!isset($condResult)) {
1516+
throw new ShouldNotHappenException();
1517+
}
1518+
if ($this->polluteScopeWithLoopInitialAssignments) {
1519+
$finalScope = $condResult->getFalseyScope()->mergeWith($initScope);
1520+
} else {
1521+
$finalScope = $condResult->getFalseyScope()->mergeWith($scope);
1522+
}
1523+
return new StatementResult(
1524+
$finalScope,
1525+
$hasYield,
1526+
false,
1527+
[],
1528+
$throwPoints,
1529+
$impurePoints,
1530+
);
1531+
}
1532+
14971533
if ($context->isTopLevel()) {
14981534
$count = 0;
14991535
do {

0 commit comments

Comments
 (0)