Skip to content

Commit 5418d4f

Browse files
committed
Fix "Cannot use [] for reading" false positive for by-reference args
- Enter expression assign context for ArrayDimFetch with null dim when passed as a by-reference argument in NodeScopeResolver::processArgs() - Exit expression assign after processing to prevent scope leakage - Add regression test in tests/PHPStan/Rules/Arrays/data/bug-5290.php - Uncomment previously disabled test case for by-ref closure in existing test data Closes phpstan/phpstan#5290
1 parent f077b36 commit 5418d4f

File tree

4 files changed

+37
-1
lines changed

4 files changed

+37
-1
lines changed

src/Analyser/NodeScopeResolver.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3401,11 +3401,18 @@ public function processArgs(
34013401
$this->storeBeforeScope($storage, $arg->value, $scopeToPass);
34023402
} else {
34033403
$exprType = $scope->getType($arg->value);
3404+
$enterExpressionAssignForByRef = $assignByReference && $arg->value instanceof ArrayDimFetch && $arg->value->dim === null;
3405+
if ($enterExpressionAssignForByRef) {
3406+
$scopeToPass = $scopeToPass->enterExpressionAssign($arg->value);
3407+
}
34043408
$exprResult = $this->processExprNode($stmt, $arg->value, $scopeToPass, $storage, $nodeCallback, $context->enterDeep());
34053409
$throwPoints = array_merge($throwPoints, $exprResult->getThrowPoints());
34063410
$impurePoints = array_merge($impurePoints, $exprResult->getImpurePoints());
34073411
$isAlwaysTerminating = $isAlwaysTerminating || $exprResult->isAlwaysTerminating();
34083412
$scope = $exprResult->getScope();
3413+
if ($enterExpressionAssignForByRef) {
3414+
$scope = $scope->exitExpressionAssign($arg->value);
3415+
}
34093416
$hasYield = $hasYield || $exprResult->hasYield();
34103417

34113418
if ($exprType->isCallable()->yes()) {

tests/PHPStan/Rules/Arrays/OffsetAccessWithoutDimForReadingRuleTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ protected function getRule(): Rule
1616
return new OffsetAccessWithoutDimForReadingRule();
1717
}
1818

19+
public function testBug5290(): void
20+
{
21+
$this->analyse([__DIR__ . '/data/bug-5290.php'], []);
22+
}
23+
1924
public function testOffsetAccessWithoutDimForReading(): void
2025
{
2126
$this->analyse(
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace Bug5290;
4+
5+
function set(?bool &$value): void {
6+
$value = true;
7+
}
8+
9+
$array = [];
10+
set($array[]);
11+
12+
var_dump($array);
13+
14+
// Also test with closures and anonymous functions
15+
(function (&$ref) {})($array[]);
16+
17+
class Foo {
18+
public function bar(?bool &$value): void {
19+
$value = true;
20+
}
21+
}
22+
23+
$foo = new Foo();
24+
$foo->bar($array[]);

tests/PHPStan/Rules/Arrays/data/offset-access-without-dim-for-reading.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
$firstElement = &$array[];
1717
(function ($ref) {})($array[]);
18-
//(function (&$ref) {})($array[]); // Should work but doesn't
18+
(function (&$ref) {})($array[]);
1919

2020
// Technically works but makes no sense
2121
$array[] += 20;

0 commit comments

Comments
 (0)