Skip to content

Commit 011505b

Browse files
Introduce reportCastedArrayKey parameter
1 parent 02066c7 commit 011505b

9 files changed

+142
-7
lines changed

conf/config.level3.neon

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,15 @@ services:
3535
class: PHPStan\Rules\Arrays\InvalidKeyInArrayDimFetchRule
3636
arguments:
3737
reportMaybes: %reportMaybes%
38+
reportCastedArrayKey: %reportCastedArrayKey%
3839
tags:
3940
- phpstan.rules.rule
4041

4142
-
4243
class: PHPStan\Rules\Arrays\InvalidKeyInArrayItemRule
4344
arguments:
4445
reportMaybes: %reportMaybes%
46+
reportCastedArrayKey: %reportCastedArrayKey%
4547
tags:
4648
- phpstan.rules.rule
4749

conf/config.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ parameters:
6363
reportStaticMethodSignatures: false
6464
reportWrongPhpDocTypeInVarTag: false
6565
reportAnyTypeWideningInVarTag: false
66+
reportCastedArrayKey: false
6667
reportPossiblyNonexistentGeneralArrayOffset: false
6768
reportPossiblyNonexistentConstantArrayOffset: false
6869
checkMissingOverrideMethodAttribute: false

conf/parametersSchema.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ parametersSchema:
7575
reportStaticMethodSignatures: bool()
7676
reportWrongPhpDocTypeInVarTag: bool()
7777
reportAnyTypeWideningInVarTag: bool()
78+
reportCastedArrayKey: bool()
7879
reportPossiblyNonexistentGeneralArrayOffset: bool()
7980
reportPossiblyNonexistentConstantArrayOffset: bool()
8081
checkMissingOverrideMethodAttribute: bool()

src/Rules/Arrays/AllowedArrayKeysTypes.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,15 @@
2121
final class AllowedArrayKeysTypes
2222
{
2323

24-
public static function getType(): Type
24+
public static function getType(bool $strict = false): Type
2525
{
26+
if ($strict) {
27+
return new UnionType([
28+
new IntegerType(),
29+
new StringType(),
30+
]);
31+
}
32+
2633
return new UnionType([
2734
new IntegerType(),
2835
new StringType(),

src/Rules/Arrays/InvalidKeyInArrayDimFetchRule.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ final class InvalidKeyInArrayDimFetchRule implements Rule
2222
public function __construct(
2323
private RuleLevelHelper $ruleLevelHelper,
2424
private bool $reportMaybes,
25+
private bool $reportCastedArrayKey,
2526
)
2627
{
2728
}
@@ -42,18 +43,19 @@ public function processNode(Node $node, Scope $scope): array
4243
return [];
4344
}
4445

46+
$reportCastedArrayKey = $this->reportCastedArrayKey;
4547
$varType = $this->ruleLevelHelper->findTypeToCheck(
4648
$scope,
4749
$node->var,
4850
'',
49-
static fn (Type $varType): bool => $varType->isArray()->no() || AllowedArrayKeysTypes::getType()->isSuperTypeOf($dimensionType)->yes(),
51+
static fn (Type $varType): bool => $varType->isArray()->no() || AllowedArrayKeysTypes::getType($reportCastedArrayKey)->isSuperTypeOf($dimensionType)->yes(),
5052
)->getType();
5153

5254
if ($varType instanceof ErrorType || $varType->isArray()->no()) {
5355
return [];
5456
}
5557

56-
$isSuperType = AllowedArrayKeysTypes::getType()->isSuperTypeOf($dimensionType);
58+
$isSuperType = AllowedArrayKeysTypes::getType($this->reportCastedArrayKey)->isSuperTypeOf($dimensionType);
5759
if ($isSuperType->yes() || ($isSuperType->maybe() && !$this->reportMaybes)) {
5860
return [];
5961
}

src/Rules/Arrays/InvalidKeyInArrayItemRule.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
final class InvalidKeyInArrayItemRule implements Rule
1717
{
1818

19-
public function __construct(private bool $reportMaybes)
19+
public function __construct(private bool $reportMaybes, private bool $reportCastedArrayKey)
2020
{
2121
}
2222

@@ -32,7 +32,7 @@ public function processNode(Node $node, Scope $scope): array
3232
}
3333

3434
$dimensionType = $scope->getType($node->key);
35-
$isSuperType = AllowedArrayKeysTypes::getType()->isSuperTypeOf($dimensionType);
35+
$isSuperType = AllowedArrayKeysTypes::getType($this->reportCastedArrayKey)->isSuperTypeOf($dimensionType);
3636
if ($isSuperType->no()) {
3737
return [
3838
RuleErrorBuilder::message(

tests/PHPStan/Rules/Arrays/InvalidKeyInArrayDimFetchRuleTest.php

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@
1313
class InvalidKeyInArrayDimFetchRuleTest extends RuleTestCase
1414
{
1515

16+
private bool $reportCastedArrayKey = false;
17+
1618
protected function getRule(): Rule
1719
{
1820
$ruleLevelHelper = new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false, true);
19-
return new InvalidKeyInArrayDimFetchRule($ruleLevelHelper, true);
21+
return new InvalidKeyInArrayDimFetchRule($ruleLevelHelper, true, $this->reportCastedArrayKey);
2022
}
2123

2224
public function testInvalidKey(): void
@@ -61,6 +63,69 @@ public function testInvalidKey(): void
6163
]);
6264
}
6365

66+
public function testInvalidKeyReportingCastedArrayKey(): void
67+
{
68+
$this->reportCastedArrayKey = true;
69+
$this->analyse([__DIR__ . '/data/invalid-key-array-dim-fetch.php'], [
70+
[
71+
'Invalid array key type null.',
72+
6,
73+
],
74+
[
75+
'Invalid array key type DateTimeImmutable.',
76+
7,
77+
],
78+
[
79+
'Invalid array key type array.',
80+
8,
81+
],
82+
[
83+
'Invalid array key type float.',
84+
10,
85+
],
86+
[
87+
'Invalid array key type true.',
88+
12,
89+
],
90+
[
91+
'Invalid array key type false.',
92+
13,
93+
],
94+
[
95+
'Possibly invalid array key type string|null.',
96+
17,
97+
],
98+
[
99+
'Possibly invalid array key type stdClass|string.',
100+
24,
101+
],
102+
[
103+
'Invalid array key type DateTimeImmutable.',
104+
31,
105+
],
106+
[
107+
'Invalid array key type DateTimeImmutable.',
108+
45,
109+
],
110+
[
111+
'Invalid array key type DateTimeImmutable.',
112+
46,
113+
],
114+
[
115+
'Invalid array key type DateTimeImmutable.',
116+
47,
117+
],
118+
[
119+
'Invalid array key type stdClass.',
120+
47,
121+
],
122+
[
123+
'Invalid array key type DateTimeImmutable.',
124+
48,
125+
],
126+
]);
127+
}
128+
64129
public function testBug6315(): void
65130
{
66131
if (PHP_VERSION_ID < 80100) {
@@ -95,4 +160,20 @@ public function testBug6315(): void
95160
]);
96161
}
97162

163+
public function testUnsetFalseKey(): void
164+
{
165+
$this->reportCastedArrayKey = true;
166+
167+
$this->analyse([__DIR__ . '/data/unset-false-key.php'], [
168+
[
169+
'Invalid array key type false.',
170+
6,
171+
],
172+
[
173+
'Invalid array key type false.',
174+
13,
175+
],
176+
]);
177+
}
178+
98179
}

tests/PHPStan/Rules/Arrays/InvalidKeyInArrayItemRuleTest.php

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@
1212
class InvalidKeyInArrayItemRuleTest extends RuleTestCase
1313
{
1414

15+
private bool $reportCastedArrayKey = false;
16+
1517
protected function getRule(): Rule
1618
{
17-
return new InvalidKeyInArrayItemRule(true);
19+
return new InvalidKeyInArrayItemRule(true, $this->reportCastedArrayKey);
1820
}
1921

2022
public function testInvalidKey(): void
@@ -35,6 +37,29 @@ public function testInvalidKey(): void
3537
]);
3638
}
3739

40+
public function testInvalidKeyReportingCastedArrayKey(): void
41+
{
42+
$this->reportCastedArrayKey = true;
43+
$this->analyse([__DIR__ . '/data/invalid-key-array-item.php'], [
44+
[
45+
'Invalid array key type null.',
46+
12,
47+
],
48+
[
49+
'Invalid array key type DateTimeImmutable.',
50+
13,
51+
],
52+
[
53+
'Invalid array key type array.',
54+
14,
55+
],
56+
[
57+
'Possibly invalid array key type stdClass|string.',
58+
15,
59+
],
60+
]);
61+
}
62+
3863
public function testInvalidKeyInList(): void
3964
{
4065
$this->analyse([__DIR__ . '/data/invalid-key-list.php'], [
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace UnsetFalseKey;
4+
5+
/** @var array<int, int> $data */
6+
unset($data[false]);
7+
8+
function test_remove_element(): void {
9+
$modified = [1, 4, 6, 8];
10+
11+
// this would happen in the SUT
12+
unset($modified[array_search(4, $modified, true)]);
13+
unset($modified[array_search(5, $modified, true)]); // bug is here - will unset key `0` by accident
14+
15+
assert([1, 6, 8] === $modified); // actually is [6, 8]
16+
}

0 commit comments

Comments
 (0)