Skip to content

Commit 854635f

Browse files
authored
[TypeDeclarationDocblocks] Guard DocblockVarArrayFromPropertyDefaultsRector against unsound narrowing (#8435)
1 parent c3088b7 commit 854635f

4 files changed

Lines changed: 57 additions & 2 deletions

File tree

‎rules-tests/TypeDeclarationDocblocks/Rector/Class_/DocblockVarArrayFromPropertyDefaultsRector/Fixture/array_union_generic_same_type.php.inc‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use stdClass;
77

88
class ArrayUnionGenericSameType
99
{
10-
protected array $dependencies = [
10+
private array $dependencies = [
1111
stdClass::class,
1212
DateTime::class,
1313
];
@@ -27,7 +27,7 @@ class ArrayUnionGenericSameType
2727
/**
2828
* @var array<int, class-string<\stdClass>|class-string<\DateTime>>
2929
*/
30-
protected array $dependencies = [
30+
private array $dependencies = [
3131
stdClass::class,
3232
DateTime::class,
3333
];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace Rector\Tests\TypeDeclarationDocblocks\Rector\Class_\DocblockVarArrayFromPropertyDefaultsRector\Fixture;
4+
5+
final class SkipEmptyNestedArray
6+
{
7+
private array $payload = ['create' => [], 'update' => []];
8+
9+
public function add(string $key, array $item): void
10+
{
11+
$this->payload['create'][$key] = $item;
12+
}
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace Rector\Tests\TypeDeclarationDocblocks\Rector\Class_\DocblockVarArrayFromPropertyDefaultsRector\Fixture;
4+
5+
class SkipNonPrivateProperty
6+
{
7+
protected array $configParams = ['enabled' => true, 'name' => 'value'];
8+
}

‎rules/TypeDeclarationDocblocks/Rector/Class_/DocblockVarArrayFromPropertyDefaultsRector.php‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use PhpParser\Comment\Doc;
88
use PhpParser\Node;
9+
use PhpParser\Node\ArrayItem;
910
use PhpParser\Node\Expr\Array_;
1011
use PhpParser\Node\Identifier;
1112
use PhpParser\Node\PropertyItem;
@@ -80,6 +81,11 @@ public function refactor(Node $node): ?Node
8081
continue;
8182
}
8283

84+
// only private properties are safe; a protected/public one can be reassigned with a wider type by a child class we cannot see here
85+
if (! $property->isPrivate()) {
86+
continue;
87+
}
88+
8389
if (count($property->props) > 1) {
8490
continue;
8591
}
@@ -89,6 +95,11 @@ public function refactor(Node $node): ?Node
8995
continue;
9096
}
9197

98+
// an empty nested array default generalizes to never[], which then rejects every real value assigned later
99+
if ($this->hasEmptyNestedArray($soleProperty->default)) {
100+
continue;
101+
}
102+
92103
$propertyDefaultType = $this->getType($soleProperty->default);
93104

94105
$propertyPhpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($property);
@@ -118,6 +129,29 @@ public function refactor(Node $node): ?Node
118129
return $node;
119130
}
120131

132+
private function hasEmptyNestedArray(Array_ $array): bool
133+
{
134+
foreach ($array->items as $arrayItem) {
135+
if (! $arrayItem instanceof ArrayItem) {
136+
continue;
137+
}
138+
139+
if (! $arrayItem->value instanceof Array_) {
140+
continue;
141+
}
142+
143+
if ($arrayItem->value->items === []) {
144+
return true;
145+
}
146+
147+
if ($this->hasEmptyNestedArray($arrayItem->value)) {
148+
return true;
149+
}
150+
}
151+
152+
return false;
153+
}
154+
121155
private function hasUsefulParentPropertyVarTag(Class_ $class, Property $property, Type $propertyDefaultType): bool
122156
{
123157
$propertyName = $this->getName($property);

0 commit comments

Comments
 (0)