5
5
use ArrayAccess ;
6
6
use Closure ;
7
7
use DivisionByZeroError ;
8
+ use Iterator ;
8
9
use PhpParser \Comment \Doc ;
9
10
use PhpParser \Modifiers ;
10
11
use PhpParser \Node ;
@@ -1184,6 +1185,14 @@ private function processStmtNode(
1184
1185
$ stmt ->expr ,
1185
1186
new Array_ ([]),
1186
1187
);
1188
+ $ exprType = $ scope ->getType ($ stmt ->expr );
1189
+ $ iteratorValidExpr = null ;
1190
+ if ((new ObjectType (Iterator::class))->isSuperTypeOf ($ exprType )->yes ()) {
1191
+ $ iteratorValidExpr = new BinaryOp \Identical (
1192
+ new MethodCall ($ stmt ->expr , 'valid ' ),
1193
+ new ConstFetch (new Name \FullyQualified ('true ' )),
1194
+ );
1195
+ }
1187
1196
if ($ stmt ->expr instanceof Variable && is_string ($ stmt ->expr ->name )) {
1188
1197
$ scope = $ this ->processVarAnnotation ($ scope , [$ stmt ->expr ->name ], $ stmt );
1189
1198
}
@@ -1193,11 +1202,17 @@ private function processStmtNode(
1193
1202
1194
1203
if ($ context ->isTopLevel ()) {
1195
1204
$ originalScope = $ this ->polluteScopeWithAlwaysIterableForeach ? $ scope ->filterByTruthyValue ($ arrayComparisonExpr ) : $ scope ;
1205
+ if ($ iteratorValidExpr !== null ) {
1206
+ $ originalScope = $ originalScope ->filterByTruthyValue ($ iteratorValidExpr );
1207
+ }
1196
1208
$ bodyScope = $ this ->enterForeach ($ originalScope , $ originalScope , $ stmt );
1197
1209
$ count = 0 ;
1198
1210
do {
1199
1211
$ prevScope = $ bodyScope ;
1200
1212
$ bodyScope = $ bodyScope ->mergeWith ($ this ->polluteScopeWithAlwaysIterableForeach ? $ scope ->filterByTruthyValue ($ arrayComparisonExpr ) : $ scope );
1213
+ if ($ iteratorValidExpr !== null ) {
1214
+ $ bodyScope = $ bodyScope ->filterByTruthyValue ($ iteratorValidExpr );
1215
+ }
1201
1216
$ bodyScope = $ this ->enterForeach ($ bodyScope , $ originalScope , $ stmt );
1202
1217
$ bodyScopeResult = $ this ->processStmtNodes ($ stmt , $ stmt ->stmts , $ bodyScope , static function (): void {
1203
1218
}, $ context ->enterDeep ())->filterOutLoopExitPoints ();
@@ -1217,6 +1232,9 @@ private function processStmtNode(
1217
1232
}
1218
1233
1219
1234
$ bodyScope = $ bodyScope ->mergeWith ($ this ->polluteScopeWithAlwaysIterableForeach ? $ scope ->filterByTruthyValue ($ arrayComparisonExpr ) : $ scope );
1235
+ if ($ iteratorValidExpr !== null ) {
1236
+ $ bodyScope = $ bodyScope ->filterByTruthyValue ($ iteratorValidExpr );
1237
+ }
1220
1238
$ bodyScope = $ this ->enterForeach ($ bodyScope , $ originalScope , $ stmt );
1221
1239
$ finalScopeResult = $ this ->processStmtNodes ($ stmt , $ stmt ->stmts , $ bodyScope , $ nodeCallback , $ context )->filterOutLoopExitPoints ();
1222
1240
$ finalScope = $ finalScopeResult ->getScope ();
@@ -1227,7 +1245,6 @@ private function processStmtNode(
1227
1245
$ finalScope = $ breakExitPoint ->getScope ()->mergeWith ($ finalScope );
1228
1246
}
1229
1247
1230
- $ exprType = $ scope ->getType ($ stmt ->expr );
1231
1248
$ isIterableAtLeastOnce = $ exprType ->isIterableAtLeastOnce ();
1232
1249
if ($ exprType ->isIterable ()->no () || $ isIterableAtLeastOnce ->maybe ()) {
1233
1250
$ finalScope = $ finalScope ->mergeWith ($ scope ->filterByTruthyValue (new BooleanOr (
@@ -1250,10 +1267,14 @@ private function processStmtNode(
1250
1267
$ throwPoints = array_merge ($ throwPoints , $ finalScopeResult ->getThrowPoints ());
1251
1268
$ impurePoints = array_merge ($ impurePoints , $ finalScopeResult ->getImpurePoints ());
1252
1269
}
1253
- if (!(new ObjectType (Traversable::class))->isSuperTypeOf ($ scope -> getType ( $ stmt -> expr ) )->no ()) {
1270
+ if (!(new ObjectType (Traversable::class))->isSuperTypeOf ($ exprType )->no ()) {
1254
1271
$ throwPoints [] = ThrowPoint::createImplicit ($ scope , $ stmt ->expr );
1255
1272
}
1256
1273
1274
+ if ($ iteratorValidExpr !== null ) {
1275
+ $ finalScope = $ finalScope ->filterByFalseyValue ($ iteratorValidExpr );
1276
+ }
1277
+
1257
1278
return new StatementResult (
1258
1279
$ finalScope ,
1259
1280
$ finalScopeResult ->hasYield () || $ condResult ->hasYield (),
0 commit comments