171
171
use PHPStan \Type \ClosureType ;
172
172
use PHPStan \Type \Constant \ConstantArrayType ;
173
173
use PHPStan \Type \Constant \ConstantArrayTypeBuilder ;
174
- use PHPStan \Type \Constant \ConstantBooleanType ;
175
174
use PHPStan \Type \Constant \ConstantIntegerType ;
176
175
use PHPStan \Type \Constant \ConstantStringType ;
177
176
use PHPStan \Type \ErrorType ;
@@ -1079,7 +1078,7 @@ private function processStmtNode(
1079
1078
}
1080
1079
} elseif ($ stmt instanceof If_) {
1081
1080
$ conditionType = ($ this ->treatPhpDocTypesAsCertain ? $ scope ->getType ($ stmt ->cond ) : $ scope ->getNativeType ($ stmt ->cond ))->toBoolean ();
1082
- $ ifAlwaysTrue = $ conditionType ->isTrue ()-> yes () ;
1081
+ $ ifAlwaysTrue = $ conditionType ->isTrue ();
1083
1082
$ condResult = $ this ->processExprNode ($ stmt , $ stmt ->cond , $ scope , $ nodeCallback , ExpressionContext::createDeep (), $ context );
1084
1083
$ exitPoints = [];
1085
1084
$ throwPoints = $ overridingThrowPoints ?? $ condResult ->getThrowPoints ();
@@ -1089,47 +1088,54 @@ private function processStmtNode(
1089
1088
$ alwaysTerminating = true ;
1090
1089
$ hasYield = $ condResult ->hasYield ();
1091
1090
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 ;
1107
1109
}
1108
- $ hasYield = $ branchScopeStatementResult ->hasYield () || $ hasYield ;
1109
1110
}
1110
1111
1111
1112
$ scope = $ condResult ->getFalseyScope ();
1112
- $ lastElseIfConditionIsTrue = false ;
1113
+ $ lastElseIfConditionIsTrue = TrinaryLogic:: createNo () ;
1113
1114
1114
1115
$ condScope = $ scope ;
1115
1116
foreach ($ stmt ->elseifs as $ elseif ) {
1116
1117
$ nodeCallback ($ elseif , $ scope );
1118
+ if (!$ context ->isTopLevel ()) {
1119
+ if ($ ifAlwaysTrue ->yes () || $ lastElseIfConditionIsTrue ->yes ()) {
1120
+ continue ;
1121
+ }
1122
+ }
1117
1123
$ elseIfConditionType = ($ this ->treatPhpDocTypesAsCertain ? $ condScope ->getType ($ elseif ->cond ) : $ scope ->getNativeType ($ elseif ->cond ))->toBoolean ();
1118
1124
$ condResult = $ this ->processExprNode ($ stmt , $ elseif ->cond , $ condScope , $ nodeCallback , ExpressionContext::createDeep (), $ context );
1119
1125
$ throwPoints = array_merge ($ throwPoints , $ condResult ->getThrowPoints ());
1120
1126
$ impurePoints = array_merge ($ impurePoints , $ condResult ->getImpurePoints ());
1121
1127
$ condScope = $ condResult ->getScope ();
1122
1128
$ branchScopeStatementResult = $ this ->processStmtNodes ($ elseif , $ elseif ->stmts , $ condResult ->getTruthyScope (), $ nodeCallback , $ context );
1123
1129
1130
+ if (!$ context ->isTopLevel () && $ elseIfConditionType ->isTrue ()->no ()) {
1131
+ $ scope = $ condScope ->filterByFalseyValue ($ elseif ->cond );
1132
+ continue ;
1133
+ }
1134
+
1124
1135
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 ()
1133
1139
) {
1134
1140
$ exitPoints = array_merge ($ exitPoints , $ branchScopeStatementResult ->getExitPoints ());
1135
1141
$ throwPoints = array_merge ($ throwPoints , $ branchScopeStatementResult ->getThrowPoints ());
@@ -1147,48 +1153,48 @@ private function processStmtNode(
1147
1153
$ hasYield = $ hasYield || $ branchScopeStatementResult ->hasYield ();
1148
1154
}
1149
1155
1150
- if (
1151
- $ elseIfConditionType ->isTrue ()->yes ()
1152
- ) {
1153
- $ lastElseIfConditionIsTrue = true ;
1156
+ if ($ elseIfConditionType ->isTrue ()->yes ()) {
1157
+ $ lastElseIfConditionIsTrue = $ elseIfConditionType ->isTrue ();
1154
1158
}
1155
-
1156
1159
$ condScope = $ condScope ->filterByFalseyValue ($ elseif ->cond );
1157
1160
$ scope = $ condScope ;
1158
1161
}
1159
1162
1160
1163
if ($ stmt ->else === null ) {
1161
- if (!$ ifAlwaysTrue && !$ lastElseIfConditionIsTrue ) {
1164
+ if (!$ ifAlwaysTrue-> yes () && !$ lastElseIfConditionIsTrue-> yes () ) {
1162
1165
$ finalScope = $ scope ->mergeWith ($ finalScope );
1163
1166
$ alwaysTerminating = false ;
1164
1167
}
1165
1168
} else {
1166
1169
$ nodeCallback ($ stmt ->else , $ scope );
1167
- $ branchScopeStatementResult = $ this ->processStmtNodes ($ stmt ->else , $ stmt ->else ->stmts , $ scope , $ nodeCallback , $ context );
1168
1170
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 ();
1182
1189
}
1183
- $ hasYield = $ hasYield || $ branchScopeStatementResult ->hasYield ();
1184
1190
}
1185
1191
}
1186
1192
1187
1193
if ($ finalScope === null ) {
1188
1194
$ finalScope = $ scope ;
1189
1195
}
1190
1196
1191
- if ($ stmt ->else === null && !$ ifAlwaysTrue && !$ lastElseIfConditionIsTrue ) {
1197
+ if ($ stmt ->else === null && !$ ifAlwaysTrue-> yes () && !$ lastElseIfConditionIsTrue-> yes () ) {
1192
1198
$ endStatements [] = new EndStatementResult ($ stmt , new StatementResult ($ finalScope , $ hasYield , $ alwaysTerminating , $ exitPoints , $ throwPoints , $ impurePoints ));
1193
1199
}
1194
1200
@@ -1202,6 +1208,7 @@ private function processStmtNode(
1202
1208
$ condResult = $ this ->processExprNode ($ stmt , $ stmt ->expr , $ scope , $ nodeCallback , ExpressionContext::createDeep (), $ context );
1203
1209
$ throwPoints = $ overridingThrowPoints ?? $ condResult ->getThrowPoints ();
1204
1210
$ impurePoints = $ condResult ->getImpurePoints ();
1211
+ $ exprType = $ scope ->getType ($ stmt ->expr );
1205
1212
$ scope = $ condResult ->getScope ();
1206
1213
$ arrayComparisonExpr = new BinaryOp \NotIdentical (
1207
1214
$ stmt ->expr ,
@@ -1214,6 +1221,18 @@ private function processStmtNode(
1214
1221
$ originalScope = $ scope ;
1215
1222
$ bodyScope = $ scope ;
1216
1223
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
+
1217
1236
if ($ context ->isTopLevel ()) {
1218
1237
$ originalScope = $ this ->polluteScopeWithAlwaysIterableForeach ? $ scope ->filterByTruthyValue ($ arrayComparisonExpr ) : $ scope ;
1219
1238
$ bodyScope = $ this ->enterForeach ($ originalScope , $ originalScope , $ stmt );
@@ -1250,8 +1269,6 @@ private function processStmtNode(
1250
1269
$ finalScope = $ breakExitPoint ->getScope ()->mergeWith ($ finalScope );
1251
1270
}
1252
1271
1253
- $ exprType = $ scope ->getType ($ stmt ->expr );
1254
- $ isIterableAtLeastOnce = $ exprType ->isIterableAtLeastOnce ();
1255
1272
if ($ exprType ->isIterable ()->no () || $ isIterableAtLeastOnce ->maybe ()) {
1256
1273
$ finalScope = $ finalScope ->mergeWith ($ scope ->filterByTruthyValue (new BooleanOr (
1257
1274
new BinaryOp \Identical (
@@ -1494,6 +1511,25 @@ private function processStmtNode(
1494
1511
$ bodyScope = $ condResult ->getTruthyScope ();
1495
1512
}
1496
1513
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
+
1497
1533
if ($ context ->isTopLevel ()) {
1498
1534
$ count = 0 ;
1499
1535
do {
0 commit comments