diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 11bd88f402..10dd9a11b7 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -182,6 +182,7 @@ use PHPStan\Type\Generic\TemplateTypeMap; use PHPStan\Type\Generic\TemplateTypeVariance; use PHPStan\Type\Generic\TemplateTypeVarianceMap; +use PHPStan\Type\IntegerRangeType; use PHPStan\Type\IntegerType; use PHPStan\Type\IntersectionType; use PHPStan\Type\MixedType; @@ -1631,7 +1632,7 @@ private function processStmtNode( if ($lastCondExpr !== null) { $alwaysIterates = $alwaysIterates->and($bodyScope->getType($lastCondExpr)->toBoolean()->isTrue()); $bodyScope = $this->processExprNode($stmt, $lastCondExpr, $bodyScope, $nodeCallback, ExpressionContext::createDeep())->getTruthyScope(); - $bodyScope = $this->inferForLoopExpressions($stmt, $lastCondExpr, $bodyScope); + $bodyScope = $this->inferForLoopExpressions($stmt, $lastCondExpr, $bodyScope, $initScope); } $finalScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, $nodeCallback, $context)->filterOutLoopExitPoints(); @@ -7308,17 +7309,22 @@ private function getFilteringExprForMatchArm(Expr\Match_ $expr, array $condition ); } - private function inferForLoopExpressions(For_ $stmt, Expr $lastCondExpr, MutatingScope $bodyScope): MutatingScope + private function inferForLoopExpressions( + For_ $stmt, + Expr $lastCondExpr, + MutatingScope $bodyScope, + MutatingScope $initScope, + ): MutatingScope { // infer $items[$i] type from for ($i = 0; $i < count($items); $i++) {...} + $positiveInt = IntegerRangeType::fromInterval(0, null); if ( // $i = 0 count($stmt->init) === 1 && $stmt->init[0] instanceof Assign && $stmt->init[0]->var instanceof Variable - && $stmt->init[0]->expr instanceof Node\Scalar\Int_ - && $stmt->init[0]->expr->value === 0 + && $positiveInt->isSuperTypeOf($initScope->getType($stmt->init[0]->expr))->yes() // $i++ or ++$i && count($stmt->loop) === 1 && ($stmt->loop[0] instanceof Expr\PreInc || $stmt->loop[0] instanceof Expr\PostInc) diff --git a/tests/PHPStan/Rules/Arrays/data/report-possibly-nonexistent-array-offset.php b/tests/PHPStan/Rules/Arrays/data/report-possibly-nonexistent-array-offset.php index fc54d96f00..618eeb6cd4 100644 --- a/tests/PHPStan/Rules/Arrays/data/report-possibly-nonexistent-array-offset.php +++ b/tests/PHPStan/Rules/Arrays/data/report-possibly-nonexistent-array-offset.php @@ -58,3 +58,26 @@ public function nonEmpty(array $a): void } } + +/** + * @param array $items + */ +function skipFirstElement(array $items): void +{ + for ($i = 1; count($items) > $i; ++$i) { + $element = $items[$i]; + \PHPStan\Testing\assertType('string', $element); + } +} + +/** + * @param positive-int $skip + * @param array $items + */ +function skipByX(int $skip, array $items): void +{ + for ($i = $skip; count($items) > $i; ++$i) { + $element = $items[$i]; + \PHPStan\Testing\assertType('string', $element); + } +}