Skip to content

Commit c6c286d

Browse files
committed
fix
1 parent 15a5616 commit c6c286d

File tree

4 files changed

+147
-10
lines changed

4 files changed

+147
-10
lines changed

src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
use PHPStan\Rules\Rule;
1010
use PHPStan\Rules\RuleErrorBuilder;
1111
use PHPStan\Type\Constant\ConstantIntegerType;
12+
use PHPStan\Type\NeverType;
1213
use PHPStan\Type\TypeCombinator;
14+
use PHPStan\Type\UnionType;
1315
use function array_keys;
1416
use function count;
1517
use function implode;
@@ -37,7 +39,6 @@ public function getNodeType(): string
3739

3840
public function processNode(Node $node, Scope $scope): array
3941
{
40-
$values = [];
4142
$duplicateKeys = [];
4243
$printedValues = [];
4344
$valueLines = [];
@@ -50,7 +51,7 @@ public function processNode(Node $node, Scope $scope): array
5051
* - False means a non-scalar value was encountered and we cannot be sure of the next keys.
5152
*/
5253
$autoGeneratedIndex = null;
53-
$overallKeyType = null;
54+
$seenKeys = [];
5455
foreach ($node->getItemNodes() as $itemNode) {
5556
$item = $itemNode->getArrayItem();
5657
if ($item === null) {
@@ -77,8 +78,6 @@ public function processNode(Node $node, Scope $scope): array
7778
$autoGeneratedIndex = $autoGeneratedIndex === null
7879
? $arrayKeyValues[0]
7980
: max($autoGeneratedIndex, $arrayKeyValues[0]);
80-
} elseif (count($arrayKeyValues) > 1) {
81-
$autoGeneratedIndex = false;
8281
}
8382
}
8483

@@ -88,12 +87,32 @@ public function processNode(Node $node, Scope $scope): array
8887
continue;
8988
}
9089

91-
if ($overallKeyType === null) {
92-
$duplicate = false;
93-
$overallKeyType = $keyType;
94-
} else {
95-
$duplicate = $overallKeyType->isSuperTypeOf($keyType)->yes();
96-
$overallKeyType = TypeCombinator::union($overallKeyType, $keyType);
90+
$duplicate = false;
91+
$newValuesType = $keyType;
92+
foreach ($seenKeys as $seenKey) {
93+
if ($seenKey instanceof UnionType) {
94+
continue;
95+
}
96+
$newValuesType = TypeCombinator::remove($newValuesType, $seenKey);
97+
98+
if (
99+
!($newValuesType instanceof NeverType)
100+
&& !$newValuesType->isSuperTypeOf($seenKey)->yes()
101+
) {
102+
continue;
103+
}
104+
105+
$duplicate = true;
106+
}
107+
108+
if (!$newValuesType instanceof UnionType) {
109+
foreach ($seenKeys as $k => $seenKey) {
110+
$seenKeys[$k] = TypeCombinator::remove($seenKey, $newValuesType);
111+
}
112+
}
113+
114+
if (!$newValuesType instanceof NeverType) {
115+
$seenKeys[] = $newValuesType;
97116
}
98117

99118
foreach ($keyValues as $value) {

tests/PHPStan/Rules/Arrays/DuplicateKeysInLiteralArraysRuleTest.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,24 @@ public function testDuplicateKeys(): void
7777
"Array has 2 duplicate keys with value 'bar' (\$key, 'bar').",
7878
128,
7979
],
80+
[
81+
"Array has 2 duplicate keys with value 'bar' (\$key, 'bar').",
82+
139,
83+
],
84+
[
85+
"Array has 2 duplicate keys with value 'foo' ('foo', \$key).",
86+
151,
87+
],
88+
[
89+
"Array has 2 duplicate keys with value 'bar' ('bar', \$key).",
90+
152,
91+
],
8092
]);
8193
}
8294

95+
public function testBug13013(): void
96+
{
97+
$this->analyse([__DIR__ . '/data/bug-13013.php'], []);
98+
}
99+
83100
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?php
2+
3+
namespace Bug13013;
4+
5+
class OfferTemplateOverviewVM
6+
{
7+
/**
8+
* @var array<int, list<mixed>>
9+
*/
10+
public array $mailsGroupedByTemplate;
11+
12+
/**
13+
* @var array<int, array<MailStatus::CODE_*, int>>
14+
*/
15+
protected array $mailCounts;
16+
17+
private function __construct()
18+
{
19+
$this->mailsGroupedByTemplate = [];
20+
$this->mailCounts = [];
21+
}
22+
23+
public function countMailStates(): void
24+
{
25+
foreach ($this->mailsGroupedByTemplate as $templateId => $mails) {
26+
$this->mailCounts[$templateId] = [
27+
MailStatus::notActive()->code => 0,
28+
MailStatus::simulation()->code => 0,
29+
MailStatus::active()->code => 0,
30+
];
31+
}
32+
}
33+
}
34+
35+
final class MailStatus
36+
{
37+
private const CODE_NOT_ACTIVE = 0;
38+
39+
private const CODE_SIMULATION = 1;
40+
41+
private const CODE_ACTIVE = 2;
42+
43+
/**
44+
* @var self::CODE_*
45+
*/
46+
public int $code;
47+
48+
public string $name;
49+
50+
public string $description;
51+
52+
/**
53+
* @param self::CODE_* $status
54+
*/
55+
public function __construct(int $status, string $name, string $description)
56+
{
57+
$this->code = $status;
58+
$this->name = $name;
59+
$this->description = $description;
60+
}
61+
62+
public static function notActive(): self
63+
{
64+
return new self(self::CODE_NOT_ACTIVE, _('Pausiert'), _('Es findet kein Mailversand an Kunden statt'));
65+
}
66+
67+
public static function simulation(): self
68+
{
69+
return new self(self::CODE_SIMULATION, _('Simulation'), _('Wenn Template zugewiesen, werden im Simulationsmodus E-Mails nur in der Datenbank gespeichert und nicht an den Kunden gesendet'));
70+
}
71+
72+
public static function active(): self
73+
{
74+
return new self(self::CODE_ACTIVE, _('Aktiv'), _('Wenn Template zugewiesen, findet Mailversand an Kunden statt'));
75+
}
76+
77+
}

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,4 +130,28 @@ public function sureDuplicate(string $key): void
130130
];
131131
}
132132

133+
/**
134+
* @param 'foo'|'bar' $key
135+
*/
136+
public function sureDuplicate2(string $key): void
137+
{
138+
$a = [
139+
$key => 'foo|bar',
140+
'foo' => 'foo',
141+
'bar' => 'bar',
142+
];
143+
}
144+
145+
/**
146+
* @param 'foo'|'bar' $key
147+
*/
148+
public function sureDuplicate3(string $key): void
149+
{
150+
$a = [
151+
'foo' => 'foo',
152+
'bar' => 'bar',
153+
$key => 'foo|bar',
154+
];
155+
}
156+
133157
}

0 commit comments

Comments
 (0)