55use ArrayAccess ;
66use Closure ;
77use DivisionByZeroError ;
8+ use Iterator ;
89use PhpParser \Comment \Doc ;
910use PhpParser \Modifiers ;
1011use PhpParser \Node ;
@@ -1184,6 +1185,14 @@ private function processStmtNode(
11841185 $ stmt ->expr ,
11851186 new Array_ ([]),
11861187 );
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+ }
11871196 if ($ stmt ->expr instanceof Variable && is_string ($ stmt ->expr ->name )) {
11881197 $ scope = $ this ->processVarAnnotation ($ scope , [$ stmt ->expr ->name ], $ stmt );
11891198 }
@@ -1193,11 +1202,17 @@ private function processStmtNode(
11931202
11941203 if ($ context ->isTopLevel ()) {
11951204 $ originalScope = $ this ->polluteScopeWithAlwaysIterableForeach ? $ scope ->filterByTruthyValue ($ arrayComparisonExpr ) : $ scope ;
1205+ if ($ iteratorValidExpr !== null ) {
1206+ $ originalScope = $ originalScope ->filterByTruthyValue ($ iteratorValidExpr );
1207+ }
11961208 $ bodyScope = $ this ->enterForeach ($ originalScope , $ originalScope , $ stmt );
11971209 $ count = 0 ;
11981210 do {
11991211 $ prevScope = $ bodyScope ;
12001212 $ bodyScope = $ bodyScope ->mergeWith ($ this ->polluteScopeWithAlwaysIterableForeach ? $ scope ->filterByTruthyValue ($ arrayComparisonExpr ) : $ scope );
1213+ if ($ iteratorValidExpr !== null ) {
1214+ $ bodyScope = $ bodyScope ->filterByTruthyValue ($ iteratorValidExpr );
1215+ }
12011216 $ bodyScope = $ this ->enterForeach ($ bodyScope , $ originalScope , $ stmt );
12021217 $ bodyScopeResult = $ this ->processStmtNodes ($ stmt , $ stmt ->stmts , $ bodyScope , static function (): void {
12031218 }, $ context ->enterDeep ())->filterOutLoopExitPoints ();
@@ -1217,6 +1232,9 @@ private function processStmtNode(
12171232 }
12181233
12191234 $ bodyScope = $ bodyScope ->mergeWith ($ this ->polluteScopeWithAlwaysIterableForeach ? $ scope ->filterByTruthyValue ($ arrayComparisonExpr ) : $ scope );
1235+ if ($ iteratorValidExpr !== null ) {
1236+ $ bodyScope = $ bodyScope ->filterByTruthyValue ($ iteratorValidExpr );
1237+ }
12201238 $ bodyScope = $ this ->enterForeach ($ bodyScope , $ originalScope , $ stmt );
12211239 $ finalScopeResult = $ this ->processStmtNodes ($ stmt , $ stmt ->stmts , $ bodyScope , $ nodeCallback , $ context )->filterOutLoopExitPoints ();
12221240 $ finalScope = $ finalScopeResult ->getScope ();
@@ -1227,7 +1245,6 @@ private function processStmtNode(
12271245 $ finalScope = $ breakExitPoint ->getScope ()->mergeWith ($ finalScope );
12281246 }
12291247
1230- $ exprType = $ scope ->getType ($ stmt ->expr );
12311248 $ isIterableAtLeastOnce = $ exprType ->isIterableAtLeastOnce ();
12321249 if ($ exprType ->isIterable ()->no () || $ isIterableAtLeastOnce ->maybe ()) {
12331250 $ finalScope = $ finalScope ->mergeWith ($ scope ->filterByTruthyValue (new BooleanOr (
@@ -1250,10 +1267,14 @@ private function processStmtNode(
12501267 $ throwPoints = array_merge ($ throwPoints , $ finalScopeResult ->getThrowPoints ());
12511268 $ impurePoints = array_merge ($ impurePoints , $ finalScopeResult ->getImpurePoints ());
12521269 }
1253- if (!(new ObjectType (Traversable::class))->isSuperTypeOf ($ scope -> getType ( $ stmt -> expr ) )->no ()) {
1270+ if (!(new ObjectType (Traversable::class))->isSuperTypeOf ($ exprType )->no ()) {
12541271 $ throwPoints [] = ThrowPoint ::createImplicit ($ scope , $ stmt ->expr );
12551272 }
12561273
1274+ if ($ iteratorValidExpr !== null ) {
1275+ $ finalScope = $ finalScope ->filterByFalseyValue ($ iteratorValidExpr );
1276+ }
1277+
12571278 return new StatementResult (
12581279 $ finalScope ,
12591280 $ finalScopeResult ->hasYield () || $ condResult ->hasYield (),
0 commit comments