Skip to content

Commit ccc4f85

Browse files
committed
More correct analysis of DuplicateKeysInLiteralArraysRule with virtual nodes
1 parent 15d59ee commit ccc4f85

6 files changed

Lines changed: 112 additions & 7 deletions

File tree

src/Analyser/NodeScopeResolver.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@
4747
use PHPStan\Broker\Broker;
4848
use PHPStan\File\FileHelper;
4949
use PHPStan\Node\InClassMethodNode;
50+
use PHPStan\Node\LiteralArrayItem;
51+
use PHPStan\Node\LiteralArrayNode;
5052
use PHPStan\Parser\Parser;
5153
use PHPStan\PhpDoc\PhpDocBlock;
5254
use PHPStan\PhpDoc\Tag\ParamTag;
@@ -1322,9 +1324,12 @@ function (Scope $scope) use ($expr, $nodeCallback, $context): Scope {
13221324

13231325
$scope = $this->processExprNode($expr->var, $scope, $nodeCallback, $context->enterDeep())->getScope();
13241326
} elseif ($expr instanceof Array_) {
1327+
$itemNodes = [];
13251328
foreach ($expr->items as $arrayItem) {
1329+
$itemNodes[] = new LiteralArrayItem($scope, $arrayItem);
13261330
$scope = $this->processExprNode($arrayItem, $scope, $nodeCallback, $context->enterDeep())->getScope();
13271331
}
1332+
$nodeCallback(new LiteralArrayNode($expr, $itemNodes), $scope);
13281333
} elseif ($expr instanceof ArrayItem) {
13291334
if ($expr->key !== null) {
13301335
$scope = $this->processExprNode($expr->key, $scope, $nodeCallback, $context->enterDeep())->getScope();

src/Node/LiteralArrayItem.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Node;
4+
5+
use PhpParser\Node\Expr\ArrayItem;
6+
use PHPStan\Analyser\Scope;
7+
8+
class LiteralArrayItem
9+
{
10+
11+
/** @var Scope */
12+
private $scope;
13+
14+
/** @var ArrayItem */
15+
private $arrayItem;
16+
17+
public function __construct(Scope $scope, ArrayItem $arrayItem)
18+
{
19+
$this->scope = $scope;
20+
$this->arrayItem = $arrayItem;
21+
}
22+
23+
public function getScope(): Scope
24+
{
25+
return $this->scope;
26+
}
27+
28+
public function getArrayItem(): ArrayItem
29+
{
30+
return $this->arrayItem;
31+
}
32+
33+
}

src/Node/LiteralArrayNode.php

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

src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace PHPStan\Rules\Arrays;
44

55
use PHPStan\Analyser\Scope;
6+
use PHPStan\Node\LiteralArrayNode;
67
use PHPStan\Rules\RuleError;
78
use PHPStan\Rules\RuleErrorBuilder;
89
use PHPStan\Type\ConstantScalarType;
@@ -22,11 +23,11 @@ public function __construct(
2223

2324
public function getNodeType(): string
2425
{
25-
return \PhpParser\Node\Expr\Array_::class;
26+
return LiteralArrayNode::class;
2627
}
2728

2829
/**
29-
* @param \PhpParser\Node\Expr\Array_ $node
30+
* @param LiteralArrayNode $node
3031
* @param \PHPStan\Analyser\Scope $scope
3132
* @return RuleError[]
3233
*/
@@ -36,16 +37,14 @@ public function processNode(\PhpParser\Node $node, Scope $scope): array
3637
$duplicateKeys = [];
3738
$printedValues = [];
3839
$valueLines = [];
39-
foreach ($node->items as $item) {
40-
if ($item === null) {
41-
continue;
42-
}
40+
foreach ($node->getItemNodes() as $itemNode) {
41+
$item = $itemNode->getArrayItem();
4342
if ($item->key === null) {
4443
continue;
4544
}
4645

4746
$key = $item->key;
48-
$keyType = $scope->getType($key);
47+
$keyType = $itemNode->getScope()->getType($key);
4948
if (
5049
!$keyType instanceof ConstantScalarType
5150
) {

tests/PHPStan/Rules/Arrays/DuplicateKeysInLiteralArraysRuleTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ public function testDuplicateKeys(): void
3232
'Array has 2 duplicate keys with value \'=\' (self::EQ, self::IS).',
3333
32,
3434
],
35+
[
36+
'Array has 2 duplicate keys with value 2 ($idx, $idx).',
37+
55,
38+
],
3539
]);
3640
}
3741

tests/PHPStan/Rules/Arrays/data/duplicate-keys.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,26 @@ public function doBar()
3535
];
3636
}
3737

38+
public function doIncrement()
39+
{
40+
$idx = 0;
41+
42+
$foo = [
43+
$idx++ => 'test',
44+
$idx++ => 'presto',
45+
];
46+
}
47+
48+
public function doIncrement2()
49+
{
50+
$idx = 0;
51+
52+
$foo = [
53+
$idx++ => 'test',
54+
$idx++ => 'presto',
55+
$idx => 'lorem',
56+
$idx => 'ipsum',
57+
];
58+
}
59+
3860
}

0 commit comments

Comments
 (0)