Skip to content

Commit b6bbbaf

Browse files
committed
Fixed type seen by IterableInForeachRule with inline @var right above foreach
1 parent d5e6df7 commit b6bbbaf

File tree

5 files changed

+72
-4
lines changed

5 files changed

+72
-4
lines changed

src/Analyser/NodeScopeResolver.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
use PHPStan\Node\InClassMethodNode;
8181
use PHPStan\Node\InClassNode;
8282
use PHPStan\Node\InClosureNode;
83+
use PHPStan\Node\InForeachNode;
8384
use PHPStan\Node\InFunctionNode;
8485
use PHPStan\Node\InstantiationCallableNode;
8586
use PHPStan\Node\LiteralArrayItem;
@@ -765,6 +766,11 @@ private function processStmtNode(
765766
$stmt->expr,
766767
new Array_([]),
767768
);
769+
$inForeachScope = $scope;
770+
if ($stmt->expr instanceof Variable && is_string($stmt->expr->name)) {
771+
$inForeachScope = $this->processVarAnnotation($scope, [$stmt->expr->name], $stmt);
772+
}
773+
$nodeCallback(new InForeachNode($stmt), $inForeachScope);
768774
$bodyScope = $this->enterForeach($scope->filterByTruthyValue($arrayComparisonExpr), $stmt);
769775
$count = 0;
770776
do {

src/Node/InForeachNode.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Node;
4+
5+
use PhpParser\Node\Stmt\Foreach_;
6+
use PhpParser\NodeAbstract;
7+
8+
class InForeachNode extends NodeAbstract implements VirtualNode
9+
{
10+
11+
public function __construct(private Foreach_ $originalNode)
12+
{
13+
parent::__construct($originalNode->getAttributes());
14+
}
15+
16+
public function getOriginalNode(): Foreach_
17+
{
18+
return $this->originalNode;
19+
}
20+
21+
public function getType(): string
22+
{
23+
return 'PHPStan_Node_InForeachNode';
24+
}
25+
26+
/**
27+
* @return string[]
28+
*/
29+
public function getSubNodeNames(): array
30+
{
31+
return [];
32+
}
33+
34+
}

src/Rules/Arrays/IterableInForeachRule.php

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use PhpParser\Node;
66
use PHPStan\Analyser\Scope;
7+
use PHPStan\Node\InForeachNode;
78
use PHPStan\Rules\Rule;
89
use PHPStan\Rules\RuleErrorBuilder;
910
use PHPStan\Rules\RuleLevelHelper;
@@ -13,7 +14,7 @@
1314
use function sprintf;
1415

1516
/**
16-
* @implements Rule<Node\Stmt\Foreach_>
17+
* @implements Rule<InForeachNode>
1718
*/
1819
class IterableInForeachRule implements Rule
1920
{
@@ -24,14 +25,15 @@ public function __construct(private RuleLevelHelper $ruleLevelHelper)
2425

2526
public function getNodeType(): string
2627
{
27-
return Node\Stmt\Foreach_::class;
28+
return InForeachNode::class;
2829
}
2930

3031
public function processNode(Node $node, Scope $scope): array
3132
{
33+
$originalNode = $node->getOriginalNode();
3234
$typeResult = $this->ruleLevelHelper->findTypeToCheck(
3335
$scope,
34-
$node->expr,
36+
$originalNode->expr,
3537
'Iterating over an object of an unknown class %s.',
3638
static fn (Type $type): bool => $type->isIterable()->yes(),
3739
);
@@ -47,7 +49,7 @@ public function processNode(Node $node, Scope $scope): array
4749
RuleErrorBuilder::message(sprintf(
4850
'Argument of an invalid type %s supplied for foreach, only iterables are supported.',
4951
$type->describe(VerbosityLevel::typeOnly()),
50-
))->line($node->expr->getLine())->build(),
52+
))->line($originalNode->expr->getLine())->build(),
5153
];
5254
}
5355

tests/PHPStan/Rules/Arrays/IterableInForeachRuleTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,10 @@ public function testRuleWithNullsafeVariant(): void
6969
]);
7070
}
7171

72+
public function testBug6564(): void
73+
{
74+
$this->checkExplicitMixed = true;
75+
$this->analyse([__DIR__ . '/data/bug-6564.php'], []);
76+
}
77+
7278
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace Bug6564;
4+
5+
class HelloWorld
6+
{
7+
public function exportFileDataProvider(mixed $value): void
8+
{
9+
if ($this->isValueIterable()) {
10+
/** @var mixed[] $value */
11+
foreach ($value as $_value) {
12+
}
13+
}
14+
15+
16+
}
17+
public function isValueIterable(): bool {
18+
return true;
19+
}
20+
}

0 commit comments

Comments
 (0)