@@ -1487,6 +1487,8 @@ private function processStmtNode(
1487
1487
$ initScope = $ condResult ->getScope ();
1488
1488
$ condResultScope = $ condResult ->getScope ();
1489
1489
1490
+ // only the last condition expression is relevant whether the loop continues
1491
+ // see https://www.php.net/manual/en/control-structures.for.php
1490
1492
if ($ condExpr === $ lastCondExpr ) {
1491
1493
$ condTruthiness = ($ this ->treatPhpDocTypesAsCertain ? $ condResultScope ->getType ($ condExpr ) : $ condResultScope ->getNativeType ($ condExpr ))->toBoolean ();
1492
1494
$ isIterableAtLeastOnce = $ isIterableAtLeastOnce ->and ($ condTruthiness ->isTrue ());
@@ -1513,6 +1515,7 @@ private function processStmtNode(
1513
1515
foreach ($ bodyScopeResult ->getExitPointsByType (Continue_::class) as $ continueExitPoint ) {
1514
1516
$ bodyScope = $ bodyScope ->mergeWith ($ continueExitPoint ->getScope ());
1515
1517
}
1518
+
1516
1519
foreach ($ stmt ->loop as $ loopExpr ) {
1517
1520
$ exprResult = $ this ->processExprNode ($ stmt , $ loopExpr , $ bodyScope , static function (): void {
1518
1521
}, ExpressionContext::createTopLevel ());
@@ -1539,6 +1542,7 @@ private function processStmtNode(
1539
1542
if ($ lastCondExpr !== null ) {
1540
1543
$ alwaysIterates = $ alwaysIterates ->and ($ bodyScope ->getType ($ lastCondExpr )->toBoolean ()->isTrue ());
1541
1544
$ bodyScope = $ this ->processExprNode ($ stmt , $ lastCondExpr , $ bodyScope , $ nodeCallback , ExpressionContext::createDeep ())->getTruthyScope ();
1545
+ $ bodyScope = $ this ->inferForLoopExpressions ($ stmt , $ lastCondExpr , $ bodyScope );
1542
1546
}
1543
1547
1544
1548
$ finalScopeResult = $ this ->processStmtNodes ($ stmt , $ stmt ->stmts , $ bodyScope , $ nodeCallback , $ context )->filterOutLoopExitPoints ();
@@ -7116,4 +7120,66 @@ public function getFilteringExprForMatchArm(Expr\Match_ $expr, array $conditions
7116
7120
);
7117
7121
}
7118
7122
7123
+ private function inferForLoopExpressions (For_ $ stmt , Expr $ lastCondExpr , MutatingScope $ bodyScope ): MutatingScope
7124
+ {
7125
+ // infer $items[$i] type from for ($i = 0; $i < count($items); $i++) {...}
7126
+
7127
+ if (
7128
+ // $i = 0
7129
+ count ($ stmt ->init ) === 1
7130
+ && $ stmt ->init [0 ] instanceof Assign
7131
+ && $ stmt ->init [0 ]->var instanceof Variable
7132
+ && $ stmt ->init [0 ]->expr instanceof Node \Scalar \Int_
7133
+ && $ stmt ->init [0 ]->expr ->value === 0
7134
+ // $i++ or ++$i
7135
+ && count ($ stmt ->loop ) === 1
7136
+ && ($ stmt ->loop [0 ] instanceof Expr \PreInc || $ stmt ->loop [0 ] instanceof Expr \PostInc)
7137
+ && $ stmt ->loop [0 ]->var instanceof Variable
7138
+ ) {
7139
+ // $i < count($items)
7140
+ if (
7141
+ $ lastCondExpr instanceof BinaryOp \Smaller
7142
+ && $ lastCondExpr ->left instanceof Variable
7143
+ && $ lastCondExpr ->right instanceof FuncCall
7144
+ && $ lastCondExpr ->right ->name instanceof Name
7145
+ && $ lastCondExpr ->right ->name ->toLowerString () === 'count '
7146
+ && count ($ lastCondExpr ->right ->getArgs ()) > 0
7147
+ && $ lastCondExpr ->right ->getArgs ()[0 ]->value instanceof Variable
7148
+ && is_string ($ stmt ->init [0 ]->var ->name )
7149
+ && $ stmt ->init [0 ]->var ->name === $ stmt ->loop [0 ]->var ->name
7150
+ && $ stmt ->init [0 ]->var ->name === $ lastCondExpr ->left ->name
7151
+ ) {
7152
+ $ arrayArg = $ lastCondExpr ->right ->getArgs ()[0 ]->value ;
7153
+ $ bodyScope = $ bodyScope ->assignExpression (
7154
+ new ArrayDimFetch ($ lastCondExpr ->right ->getArgs ()[0 ]->value , $ lastCondExpr ->left ),
7155
+ $ bodyScope ->getType ($ arrayArg )->getIterableValueType (),
7156
+ $ bodyScope ->getNativeType ($ arrayArg )->getIterableValueType (),
7157
+ );
7158
+ }
7159
+
7160
+ // count($items) > $i
7161
+ if (
7162
+ $ lastCondExpr instanceof BinaryOp \Greater
7163
+ && $ lastCondExpr ->right instanceof Variable
7164
+ && $ lastCondExpr ->left instanceof FuncCall
7165
+ && $ lastCondExpr ->left ->name instanceof Name
7166
+ && $ lastCondExpr ->left ->name ->toLowerString () === 'count '
7167
+ && count ($ lastCondExpr ->left ->getArgs ()) > 0
7168
+ && $ lastCondExpr ->left ->getArgs ()[0 ]->value instanceof Variable
7169
+ && is_string ($ stmt ->init [0 ]->var ->name )
7170
+ && $ stmt ->init [0 ]->var ->name === $ stmt ->loop [0 ]->var ->name
7171
+ && $ stmt ->init [0 ]->var ->name === $ lastCondExpr ->right ->name
7172
+ ) {
7173
+ $ arrayArg = $ lastCondExpr ->left ->getArgs ()[0 ]->value ;
7174
+ $ bodyScope = $ bodyScope ->assignExpression (
7175
+ new ArrayDimFetch ($ lastCondExpr ->left ->getArgs ()[0 ]->value , $ lastCondExpr ->right ),
7176
+ $ bodyScope ->getType ($ arrayArg )->getIterableValueType (),
7177
+ $ bodyScope ->getNativeType ($ arrayArg )->getIterableValueType (),
7178
+ );
7179
+ }
7180
+ }
7181
+
7182
+ return $ bodyScope ;
7183
+ }
7184
+
7119
7185
}
0 commit comments