Skip to content

Commit 579d38f

Browse files
github-actions[bot]phpstan-bot
authored andcommitted
Fix bogus write-only error when appending to ArrayAccess property
- In ClassStatementsGatherer, also record a PropertyRead when a PropertyAssignNode is for an ArrayAccess property via array dim fetch (e.g. $this->array[] = $s), because this is semantically $this->array->offsetSet() which reads the property - New regression test in tests/PHPStan/Rules/DeadCode/data/bug-6777.php - Updated existing WriteToCollection test expectations Closes phpstan/phpstan#6777
1 parent 1bbe9dc commit 579d38f

3 files changed

Lines changed: 39 additions & 11 deletions

File tree

src/Node/ClassStatementsGatherer.php

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,14 @@
1313
use PhpParser\Node\Identifier;
1414
use PHPStan\Analyser\Scope;
1515
use PHPStan\Node\Constant\ClassConstantFetch;
16+
use PHPStan\Node\Expr\SetExistingOffsetValueTypeExpr;
17+
use PHPStan\Node\Expr\SetOffsetValueTypeExpr;
1618
use PHPStan\Node\Property\PropertyAssign;
1719
use PHPStan\Node\Property\PropertyRead;
1820
use PHPStan\Node\Property\PropertyWrite;
1921
use PHPStan\Reflection\ClassReflection;
2022
use PHPStan\ShouldNotHappenException;
23+
use PHPStan\Type\ObjectType;
2124
use PHPStan\Type\TypeUtils;
2225
use ReflectionProperty;
2326
use function count;
@@ -200,7 +203,18 @@ private function gatherNodes(Node $node, Scope $scope): void
200203
return;
201204
}
202205
if ($node instanceof PropertyAssignNode) {
203-
$this->propertyUsages[] = new PropertyWrite($node->getPropertyFetch(), $scope, false);
206+
$propertyFetch = $node->getPropertyFetch();
207+
$assignedExpr = $node->getAssignedExpr();
208+
if ($assignedExpr instanceof SetOffsetValueTypeExpr || $assignedExpr instanceof SetExistingOffsetValueTypeExpr) {
209+
$propertyType = $scope->getType($propertyFetch);
210+
if (
211+
!$propertyType->isArray()->yes()
212+
&& (new ObjectType(\ArrayAccess::class))->isSuperTypeOf($propertyType)->yes()
213+
) {
214+
$this->propertyUsages[] = new PropertyRead($propertyFetch, $scope);
215+
}
216+
}
217+
$this->propertyUsages[] = new PropertyWrite($propertyFetch, $scope, false);
204218
$this->propertyAssigns[] = new PropertyAssign($node, $scope);
205219
return;
206220
}

tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -130,16 +130,6 @@ public function testRule(): void
130130
191,
131131
$tip,
132132
],
133-
[
134-
'Property UnusedPrivateProperty\WriteToCollection::$collection1 is never read, only written.',
135-
221,
136-
$tip,
137-
],
138-
[
139-
'Property UnusedPrivateProperty\WriteToCollection::$collection2 is never read, only written.',
140-
224,
141-
$tip,
142-
],
143133
]);
144134
$this->analyse([__DIR__ . '/data/TestExtension.php'], [
145135
[
@@ -410,4 +400,13 @@ public function testBug9213(): void
410400
$this->analyse([__DIR__ . '/data/bug-9213.php'], []);
411401
}
412402

403+
#[RequiresPhp('>= 8.0')]
404+
public function testBug6777(): void
405+
{
406+
$this->alwaysWrittenTags = [];
407+
$this->alwaysReadTags = [];
408+
409+
$this->analyse([__DIR__ . '/data/bug-6777.php'], []);
410+
}
411+
413412
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php // lint >= 8.0
2+
3+
declare(strict_types = 1);
4+
5+
namespace Bug6777;
6+
7+
class HelloWorld
8+
{
9+
/** @param \ArrayObject<int, string> $array */
10+
public function __construct(private \ArrayObject $array){}
11+
12+
public function send(string $s) : void{
13+
$this->array[] = $s;
14+
}
15+
}

0 commit comments

Comments
 (0)