Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions src/Analyser/NodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -1631,7 +1632,7 @@
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();
Expand Down Expand Up @@ -7308,17 +7309,22 @@
);
}

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()

Check warning on line 7327 in src/Analyser/NodeScopeResolver.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.3, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ { // infer $items[$i] type from for ($i = 0; $i < count($items); $i++) {...} $positiveInt = IntegerRangeType::fromInterval(0, null); - if (count($stmt->init) === 1 && $stmt->init[0] instanceof Assign && $stmt->init[0]->var instanceof Variable && $positiveInt->isSuperTypeOf($initScope->getType($stmt->init[0]->expr))->yes() && count($stmt->loop) === 1 && ($stmt->loop[0] instanceof Expr\PreInc || $stmt->loop[0] instanceof Expr\PostInc) && $stmt->loop[0]->var instanceof Variable) { + if (count($stmt->init) === 1 && $stmt->init[0] instanceof Assign && $stmt->init[0]->var instanceof Variable && !$positiveInt->isSuperTypeOf($initScope->getType($stmt->init[0]->expr))->no() && count($stmt->loop) === 1 && ($stmt->loop[0] instanceof Expr\PreInc || $stmt->loop[0] instanceof Expr\PostInc) && $stmt->loop[0]->var instanceof Variable) { // $i < count($items) if ($lastCondExpr instanceof BinaryOp\Smaller && $lastCondExpr->left instanceof Variable && $lastCondExpr->right instanceof FuncCall && $lastCondExpr->right->name instanceof Name && $lastCondExpr->right->name->toLowerString() === 'count' && count($lastCondExpr->right->getArgs()) > 0 && $lastCondExpr->right->getArgs()[0]->value instanceof Variable && is_string($stmt->init[0]->var->name) && $stmt->init[0]->var->name === $stmt->loop[0]->var->name && $stmt->init[0]->var->name === $lastCondExpr->left->name) { $arrayArg = $lastCondExpr->right->getArgs()[0]->value;

Check warning on line 7327 in src/Analyser/NodeScopeResolver.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.2, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ { // infer $items[$i] type from for ($i = 0; $i < count($items); $i++) {...} $positiveInt = IntegerRangeType::fromInterval(0, null); - if (count($stmt->init) === 1 && $stmt->init[0] instanceof Assign && $stmt->init[0]->var instanceof Variable && $positiveInt->isSuperTypeOf($initScope->getType($stmt->init[0]->expr))->yes() && count($stmt->loop) === 1 && ($stmt->loop[0] instanceof Expr\PreInc || $stmt->loop[0] instanceof Expr\PostInc) && $stmt->loop[0]->var instanceof Variable) { + if (count($stmt->init) === 1 && $stmt->init[0] instanceof Assign && $stmt->init[0]->var instanceof Variable && !$positiveInt->isSuperTypeOf($initScope->getType($stmt->init[0]->expr))->no() && count($stmt->loop) === 1 && ($stmt->loop[0] instanceof Expr\PreInc || $stmt->loop[0] instanceof Expr\PostInc) && $stmt->loop[0]->var instanceof Variable) { // $i < count($items) if ($lastCondExpr instanceof BinaryOp\Smaller && $lastCondExpr->left instanceof Variable && $lastCondExpr->right instanceof FuncCall && $lastCondExpr->right->name instanceof Name && $lastCondExpr->right->name->toLowerString() === 'count' && count($lastCondExpr->right->getArgs()) > 0 && $lastCondExpr->right->getArgs()[0]->value instanceof Variable && is_string($stmt->init[0]->var->name) && $stmt->init[0]->var->name === $stmt->loop[0]->var->name && $stmt->init[0]->var->name === $lastCondExpr->left->name) { $arrayArg = $lastCondExpr->right->getArgs()[0]->value;

Check warning on line 7327 in src/Analyser/NodeScopeResolver.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.4, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ { // infer $items[$i] type from for ($i = 0; $i < count($items); $i++) {...} $positiveInt = IntegerRangeType::fromInterval(0, null); - if (count($stmt->init) === 1 && $stmt->init[0] instanceof Assign && $stmt->init[0]->var instanceof Variable && $positiveInt->isSuperTypeOf($initScope->getType($stmt->init[0]->expr))->yes() && count($stmt->loop) === 1 && ($stmt->loop[0] instanceof Expr\PreInc || $stmt->loop[0] instanceof Expr\PostInc) && $stmt->loop[0]->var instanceof Variable) { + if (count($stmt->init) === 1 && $stmt->init[0] instanceof Assign && $stmt->init[0]->var instanceof Variable && !$positiveInt->isSuperTypeOf($initScope->getType($stmt->init[0]->expr))->no() && count($stmt->loop) === 1 && ($stmt->loop[0] instanceof Expr\PreInc || $stmt->loop[0] instanceof Expr\PostInc) && $stmt->loop[0]->var instanceof Variable) { // $i < count($items) if ($lastCondExpr instanceof BinaryOp\Smaller && $lastCondExpr->left instanceof Variable && $lastCondExpr->right instanceof FuncCall && $lastCondExpr->right->name instanceof Name && $lastCondExpr->right->name->toLowerString() === 'count' && count($lastCondExpr->right->getArgs()) > 0 && $lastCondExpr->right->getArgs()[0]->value instanceof Variable && is_string($stmt->init[0]->var->name) && $stmt->init[0]->var->name === $stmt->loop[0]->var->name && $stmt->init[0]->var->name === $lastCondExpr->left->name) { $arrayArg = $lastCondExpr->right->getArgs()[0]->value;
// $i++ or ++$i
&& count($stmt->loop) === 1
&& ($stmt->loop[0] instanceof Expr\PreInc || $stmt->loop[0] instanceof Expr\PostInc)
Expand Down
21 changes: 21 additions & 0 deletions tests/PHPStan/Analyser/nsrt/for-loop-expr.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,24 @@ function getItemsArray(array $items): array
assertType('array<string>', $items);
return $items;
}

/**
* @param array<string> $items
*/
function skipFirstElement(array $items): void
{
for ($i = 1; count($items) > $i; ++$i) {
$items[$i] = 'hello';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The newly added test miss assertType() calls to assert type results

}
}

/**
* @param positive-int $skip
* @param array<string> $items
*/
function skipByX(int $skip, array $items): void
{
for ($i = $skip; count($items) > $i; ++$i) {
$items[$i] = 'hello';
}
}
Loading